AlternateRecentsComponent.java revision 0d767551c55d9e594a0b944bd1926c21a344b5ae
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
17package com.android.systemui.recents;
18
19import android.app.ActivityManager;
20import android.app.ActivityOptions;
21import android.content.ActivityNotFoundException;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Matrix;
31import android.graphics.Rect;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.IBinder;
35import android.os.Message;
36import android.os.Messenger;
37import android.os.RemoteException;
38import android.os.UserHandle;
39import android.util.DisplayMetrics;
40import android.view.Display;
41import android.view.Surface;
42import android.view.SurfaceControl;
43import android.view.View;
44import android.view.WindowManager;
45import com.android.systemui.R;
46
47import java.util.Iterator;
48import java.util.List;
49
50/** A proxy implementation for the recents component */
51public class AlternateRecentsComponent {
52
53    /** A handler for messages from the recents implementation */
54    class RecentsMessageHandler extends Handler {
55        @Override
56        public void handleMessage(Message msg) {
57            if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
58                Resources res = mContext.getResources();
59                float statusBarHeight = res.getDimensionPixelSize(
60                        com.android.internal.R.dimen.status_bar_height);
61                Bundle replyData = msg.getData().getParcelable("replyData");
62                mSingleCountFirstTaskRect = replyData.getParcelable("singleCountTaskRect");
63                mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight);
64                mMultipleCountFirstTaskRect = replyData.getParcelable("multipleCountTaskRect");
65                mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight);
66            }
67        }
68    }
69
70    /** A service connection to the recents implementation */
71    class RecentsServiceConnection implements ServiceConnection {
72        @Override
73        public void onServiceConnected(ComponentName className, IBinder service) {
74            Console.log(Constants.DebugFlags.App.RecentsComponent,
75                    "[RecentsComponent|ServiceConnection|onServiceConnected]",
76                    "toggleRecents: " + mToggleRecentsUponServiceBound);
77            mService = new Messenger(service);
78            mServiceIsBound = true;
79
80            // Toggle recents if this service connection was triggered by hitting the recents button
81            if (mToggleRecentsUponServiceBound) {
82                startAlternateRecentsActivity();
83            }
84            mToggleRecentsUponServiceBound = false;
85        }
86
87        @Override
88        public void onServiceDisconnected(ComponentName className) {
89            Console.log(Constants.DebugFlags.App.RecentsComponent,
90                    "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
91            mService = null;
92            mServiceIsBound = false;
93        }
94    }
95
96    final static int MSG_UPDATE_FOR_CONFIGURATION = 0;
97    final static int MSG_UPDATE_TASK_THUMBNAIL = 1;
98    final static int MSG_PRELOAD_TASKS = 2;
99    final static int MSG_CANCEL_PRELOAD_TASKS = 3;
100    final static int MSG_CLOSE_RECENTS = 4;
101    final static int MSG_TOGGLE_RECENTS = 5;
102
103    final static int sMinToggleDelay = 425;
104
105    final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
106    final static String sRecentsPackage = "com.android.systemui";
107    final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
108    final static String sRecentsService = "com.android.systemui.recents.RecentsService";
109
110    Context mContext;
111    SystemServicesProxy mSystemServicesProxy;
112
113    // Recents service binding
114    Messenger mService = null;
115    Messenger mMessenger;
116    boolean mServiceIsBound = false;
117    boolean mToggleRecentsUponServiceBound;
118    RecentsServiceConnection mConnection = new RecentsServiceConnection();
119
120    View mStatusBarView;
121    Rect mSingleCountFirstTaskRect = new Rect();
122    Rect mMultipleCountFirstTaskRect = new Rect();
123    long mLastToggleTime;
124
125    public AlternateRecentsComponent(Context context) {
126        mContext = context;
127        mSystemServicesProxy = new SystemServicesProxy(context);
128        mMessenger = new Messenger(new RecentsMessageHandler());
129    }
130
131    public void onStart() {
132        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|start]");
133
134        // Try to create a long-running connection to the recents service
135        bindToRecentsService(false);
136    }
137
138    /** Toggles the alternate recents activity */
139    public void onToggleRecents(Display display, int layoutDirection, View statusBarView) {
140        Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsStartup,
141                Constants.DebugFlags.App.TimeRecentsStartupKey);
142        Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
143                Constants.DebugFlags.App.TimeRecentsLaunchKey);
144        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|toggleRecents]",
145                "serviceIsBound: " + mServiceIsBound);
146        mStatusBarView = statusBarView;
147        if (!mServiceIsBound) {
148            // Try to create a long-running connection to the recents service before toggling
149            // recents
150            bindToRecentsService(true);
151            return;
152        }
153
154        try {
155            startAlternateRecentsActivity();
156        } catch (ActivityNotFoundException e) {
157            Console.logRawError("Failed to launch RecentAppsIntent", e);
158        }
159    }
160
161    public void onPreloadRecents() {
162        // Do nothing
163    }
164
165    public void onCancelPreloadingRecents() {
166        // Do nothing
167    }
168
169    public void onCloseRecents() {
170        Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|closeRecents]");
171        if (mServiceIsBound) {
172            // Try and update the recents configuration
173            try {
174                Bundle data = new Bundle();
175                Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0);
176                msg.setData(data);
177                mService.send(msg);
178            } catch (RemoteException re) {
179                re.printStackTrace();
180            }
181        }
182    }
183
184    public void onConfigurationChanged(Configuration newConfig) {
185        if (mServiceIsBound) {
186            Resources res = mContext.getResources();
187            int statusBarHeight = res.getDimensionPixelSize(
188                    com.android.internal.R.dimen.status_bar_height);
189            int navBarHeight = res.getDimensionPixelSize(
190                    com.android.internal.R.dimen.navigation_bar_height);
191            Rect rect = new Rect();
192            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
193            wm.getDefaultDisplay().getRectSize(rect);
194
195            // Try and update the recents configuration
196            try {
197                Bundle data = new Bundle();
198                data.putParcelable("windowRect", rect);
199                data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0));
200                Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
201                msg.setData(data);
202                msg.replyTo = mMessenger;
203                mService.send(msg);
204            } catch (RemoteException re) {
205                re.printStackTrace();
206            }
207        }
208    }
209
210    /** Binds to the recents implementation */
211    private void bindToRecentsService(boolean toggleRecentsUponConnection) {
212        mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
213        Intent intent = new Intent();
214        intent.setClassName(sRecentsPackage, sRecentsService);
215        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
216    }
217
218    /** Loads the first task thumbnail */
219    Bitmap loadFirstTaskThumbnail() {
220        SystemServicesProxy ssp = mSystemServicesProxy;
221        List<ActivityManager.RecentTaskInfo> tasks = ssp.getRecentTasks(1,
222                UserHandle.CURRENT.getIdentifier());
223        for (ActivityManager.RecentTaskInfo t : tasks) {
224            // Skip tasks in the home stack
225            if (ssp.isInHomeStack(t.persistentId)) {
226                return null;
227            }
228
229            Bitmap thumbnail = ssp.getTaskThumbnail(t.persistentId);
230            return thumbnail;
231        }
232        return null;
233    }
234
235    /** Returns whether there is are multiple recents tasks */
236    boolean hasMultipleRecentsTask() {
237        // NOTE: Currently there's no method to get the number of non-home tasks, so we have to
238        // compute this ourselves
239        SystemServicesProxy ssp = mSystemServicesProxy;
240        List<ActivityManager.RecentTaskInfo> tasks = ssp.getRecentTasks(4,
241                UserHandle.CURRENT.getIdentifier());
242        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
243        while (iter.hasNext()) {
244            ActivityManager.RecentTaskInfo t = iter.next();
245
246            // Skip tasks in the home stack
247            if (ssp.isInHomeStack(t.persistentId)) {
248                iter.remove();
249                continue;
250            }
251        }
252        return (tasks.size() > 1);
253    }
254
255    /** Converts from the device rotation to the degree */
256    float getDegreesForRotation(int value) {
257        switch (value) {
258            case Surface.ROTATION_90:
259                return 360f - 90f;
260            case Surface.ROTATION_180:
261                return 360f - 180f;
262            case Surface.ROTATION_270:
263                return 360f - 270f;
264        }
265        return 0f;
266    }
267
268    /** Takes a screenshot of the surface */
269    Bitmap takeScreenshot(Display display) {
270        DisplayMetrics dm = new DisplayMetrics();
271        display.getRealMetrics(dm);
272        float[] dims = {dm.widthPixels, dm.heightPixels};
273        float degrees = getDegreesForRotation(display.getRotation());
274        boolean requiresRotation = (degrees > 0);
275        if (requiresRotation) {
276            // Get the dimensions of the device in its native orientation
277            Matrix m = new Matrix();
278            m.preRotate(-degrees);
279            m.mapPoints(dims);
280            dims[0] = Math.abs(dims[0]);
281            dims[1] = Math.abs(dims[1]);
282        }
283        return SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
284    }
285
286    /** Starts the recents activity */
287    void startAlternateRecentsActivity() {
288        // If the user has toggled it too quickly, then just eat up the event here (it's better than
289        // showing a janky screenshot).
290        // NOTE: Ideally, the screenshot mechanism would take the window transform into account
291        if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) {
292            return;
293        }
294
295        // If Recents is the front most activity, then we should just communicate with it directly
296        // to launch the first task or dismiss itself
297        SystemServicesProxy ssp = mSystemServicesProxy;
298        List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
299        boolean isTopTaskHome = false;
300        if (!tasks.isEmpty()) {
301            ActivityManager.RunningTaskInfo topTask = tasks.get(0);
302            ComponentName topActivity = topTask.topActivity;
303
304            // Check if the front most activity is recents
305            if (topActivity.getPackageName().equals(sRecentsPackage) &&
306                    topActivity.getClassName().equals(sRecentsActivity)) {
307                // Notify Recents to toggle itself
308                try {
309                    Bundle data = new Bundle();
310                    Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0);
311                    msg.setData(data);
312                    mService.send(msg);
313
314                    // Time this path
315                    Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
316                            Constants.DebugFlags.App.TimeRecentsStartupKey, "sendToggleRecents");
317                    Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask,
318                            Constants.DebugFlags.App.TimeRecentsLaunchKey, "sendToggleRecents");
319                } catch (RemoteException re) {
320                    re.printStackTrace();
321                }
322                mLastToggleTime = System.currentTimeMillis();
323                return;
324            }
325
326            // Determine whether the top task is currently home
327            isTopTaskHome = ssp.isInHomeStack(topTask.id);
328        }
329
330        // Otherwise, Recents is not the front-most activity and we should animate into it
331        boolean hasMultipleTasks = hasMultipleRecentsTask();
332        Rect taskRect = hasMultipleTasks ? mMultipleCountFirstTaskRect : mSingleCountFirstTaskRect;
333        if (!isTopTaskHome && taskRect != null && taskRect.width() > 0 && taskRect.height() > 0) {
334            // Loading from thumbnail
335            Bitmap thumbnail;
336            Bitmap firstThumbnail = loadFirstTaskThumbnail();
337            if (firstThumbnail != null) {// Create the thumbnail
338                thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
339                        Bitmap.Config.ARGB_8888);
340                int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
341                Canvas c = new Canvas(thumbnail);
342                c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
343                        new Rect(0, 0, taskRect.width(), taskRect.height()), null);
344                c.setBitmap(null);
345                // Recycle the old thumbnail
346                firstThumbnail.recycle();
347            } else {
348                // Load the thumbnail from the screenshot if can't get one from the system
349                WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
350                Display display = wm.getDefaultDisplay();
351                Bitmap screenshot = takeScreenshot(display);
352                Resources res = mContext.getResources();
353                int size = Math.min(screenshot.getWidth(), screenshot.getHeight());
354                int statusBarHeight = res.getDimensionPixelSize(
355                        com.android.internal.R.dimen.status_bar_height);
356                thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
357                        Bitmap.Config.ARGB_8888);
358                Canvas c = new Canvas(thumbnail);
359                c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size),
360                        new Rect(0, 0, taskRect.width(), taskRect.height()), null);
361                c.setBitmap(null);
362                // Recycle the temporary screenshot
363                screenshot.recycle();
364            }
365
366            ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
367                    thumbnail, taskRect.left, taskRect.top, null);
368            startAlternateRecentsActivity(opts);
369        } else {
370            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
371                    R.anim.recents_from_launcher_enter,
372                    R.anim.recents_from_launcher_exit);
373            startAlternateRecentsActivity(opts);
374        }
375
376        Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
377                Constants.DebugFlags.App.TimeRecentsStartupKey, "startRecentsActivity");
378        mLastToggleTime = System.currentTimeMillis();
379    }
380
381    /** Starts the recents activity */
382    void startAlternateRecentsActivity(ActivityOptions opts) {
383        Intent intent = new Intent(sToggleRecentsAction);
384        intent.setClassName(sRecentsPackage, sRecentsActivity);
385        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
386                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
387        if (opts != null) {
388            mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
389                    UserHandle.USER_CURRENT));
390        } else {
391            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
392        }
393    }
394}
395