1 /*
2  * Copyright (C) 2013 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.recent;
18 
19 import android.app.ActivityOptions;
20 import android.content.ActivityNotFoundException;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Paint;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.util.DisplayMetrics;
33 import android.util.Log;
34 import android.view.Display;
35 import android.view.View;
36 import com.android.systemui.R;
37 import com.android.systemui.RecentsComponent;
38 import com.android.systemui.SystemUI;
39 import com.android.systemui.recents.AlternateRecentsComponent;
40 
41 
42 public class Recents extends SystemUI implements RecentsComponent {
43     private static final String TAG = "Recents";
44     private static final boolean DEBUG = true;
45 
46     // Which recents to use
47     boolean mUseAlternateRecents = true;
48     boolean mBootCompleted = false;
49     static AlternateRecentsComponent sAlternateRecents;
50 
51     /** Returns the Recents component, creating a new one in-process if necessary. */
getRecentsComponent(Context context, boolean forceInitialize)52     public static AlternateRecentsComponent getRecentsComponent(Context context,
53             boolean forceInitialize) {
54         if (sAlternateRecents == null) {
55             sAlternateRecents = new AlternateRecentsComponent(context);
56             if (forceInitialize) {
57                 sAlternateRecents.onStart();
58                 sAlternateRecents.onBootCompleted();
59             }
60         }
61         return sAlternateRecents;
62     }
63 
64     @Override
start()65     public void start() {
66         if (mUseAlternateRecents) {
67             if (sAlternateRecents == null) {
68                 sAlternateRecents = getRecentsComponent(mContext, false);
69             }
70             sAlternateRecents.onStart();
71         }
72 
73         putComponent(RecentsComponent.class, this);
74     }
75 
76     @Override
onBootCompleted()77     protected void onBootCompleted() {
78         if (mUseAlternateRecents) {
79             if (sAlternateRecents != null) {
80                 sAlternateRecents.onBootCompleted();
81             }
82         }
83         mBootCompleted = true;
84     }
85 
86     @Override
showRecents(boolean triggeredFromAltTab, View statusBarView)87     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
88         if (mUseAlternateRecents) {
89             sAlternateRecents.onShowRecents(triggeredFromAltTab);
90         }
91     }
92 
93     @Override
hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)94     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
95         if (mUseAlternateRecents) {
96             sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
97         } else {
98             Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
99             intent.setPackage("com.android.systemui");
100             sendBroadcastSafely(intent);
101 
102             RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
103         }
104     }
105 
106     @Override
toggleRecents(Display display, int layoutDirection, View statusBarView)107     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
108         if (mUseAlternateRecents) {
109             // Launch the alternate recents if required
110             sAlternateRecents.onToggleRecents();
111             return;
112         }
113 
114         if (DEBUG) Log.d(TAG, "toggle recents panel");
115         try {
116             TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
117 
118             Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);
119             intent.setClassName("com.android.systemui",
120                     "com.android.systemui.recent.RecentsActivity");
121             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
122                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
123 
124             if (firstTask == null) {
125                 if (RecentsActivity.forceOpaqueBackground(mContext)) {
126                     ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
127                             R.anim.recents_launch_from_launcher_enter,
128                             R.anim.recents_launch_from_launcher_exit);
129                     mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
130                             UserHandle.USER_CURRENT));
131                 } else {
132                     // The correct window animation will be applied via the activity's style
133                     mContext.startActivityAsUser(intent, new UserHandle(
134                             UserHandle.USER_CURRENT));
135                 }
136 
137             } else {
138                 Bitmap first = null;
139                 if (firstTask.getThumbnail() instanceof BitmapDrawable) {
140                     first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap();
141                 } else {
142                     first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
143                     Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail();
144                     d.draw(new Canvas(first));
145                 }
146                 final Resources res = mContext.getResources();
147 
148                 float thumbWidth = res
149                         .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
150                 float thumbHeight = res
151                         .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height);
152                 if (first == null) {
153                     throw new RuntimeException("Recents thumbnail is null");
154                 }
155                 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) {
156                     first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight,
157                             true);
158                     if (first == null) {
159                         throw new RuntimeException("Recents thumbnail is null");
160                     }
161                 }
162 
163 
164                 DisplayMetrics dm = new DisplayMetrics();
165                 display.getMetrics(dm);
166                 // calculate it here, but consider moving it elsewhere
167                 // first, determine which orientation you're in.
168                 final Configuration config = res.getConfiguration();
169                 int x, y;
170 
171                 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
172                     float appLabelLeftMargin = res.getDimensionPixelSize(
173                             R.dimen.status_bar_recents_app_label_left_margin);
174                     float appLabelWidth = res.getDimensionPixelSize(
175                             R.dimen.status_bar_recents_app_label_width);
176                     float thumbLeftMargin = res.getDimensionPixelSize(
177                             R.dimen.status_bar_recents_thumbnail_left_margin);
178                     float thumbBgPadding = res.getDimensionPixelSize(
179                             R.dimen.status_bar_recents_thumbnail_bg_padding);
180 
181                     float width = appLabelLeftMargin +
182                             +appLabelWidth
183                             + thumbLeftMargin
184                             + thumbWidth
185                             + 2 * thumbBgPadding;
186 
187                     x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth
188                             + thumbBgPadding + thumbLeftMargin);
189                     y = (int) (dm.heightPixels
190                             - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height)
191                             - thumbBgPadding);
192                     if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
193                         x = dm.widthPixels - x - res.getDimensionPixelSize(
194                                 R.dimen.status_bar_recents_thumbnail_width);
195                     }
196 
197                 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
198                     float thumbTopMargin = res.getDimensionPixelSize(
199                             R.dimen.status_bar_recents_thumbnail_top_margin);
200                     float thumbBgPadding = res.getDimensionPixelSize(
201                             R.dimen.status_bar_recents_thumbnail_bg_padding);
202                     float textPadding = res.getDimensionPixelSize(
203                             R.dimen.status_bar_recents_text_description_padding);
204                     float labelTextSize = res.getDimensionPixelSize(
205                             R.dimen.status_bar_recents_app_label_text_size);
206                     Paint p = new Paint();
207                     p.setTextSize(labelTextSize);
208                     float labelTextHeight = p.getFontMetricsInt().bottom
209                             - p.getFontMetricsInt().top;
210                     float descriptionTextSize = res.getDimensionPixelSize(
211                             R.dimen.status_bar_recents_app_description_text_size);
212                     p.setTextSize(descriptionTextSize);
213                     float descriptionTextHeight = p.getFontMetricsInt().bottom
214                             - p.getFontMetricsInt().top;
215 
216                     float statusBarHeight = res.getDimensionPixelSize(
217                             com.android.internal.R.dimen.status_bar_height);
218                     float recentsItemTopPadding = statusBarHeight;
219 
220                     float height = thumbTopMargin
221                             + thumbHeight
222                             + 2 * thumbBgPadding + textPadding + labelTextHeight
223                             + recentsItemTopPadding + textPadding + descriptionTextHeight;
224                     float recentsItemRightPadding = res
225                             .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding);
226                     float recentsScrollViewRightPadding = res
227                             .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin);
228                     x = (int) (dm.widthPixels - res
229                             .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width)
230                             - thumbBgPadding - recentsItemRightPadding
231                             - recentsScrollViewRightPadding);
232                     y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin
233                             + recentsItemTopPadding + thumbBgPadding + statusBarHeight);
234                 }
235 
236                 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(
237                         statusBarView,
238                         first, x, y,
239                         new ActivityOptions.OnAnimationStartedListener() {
240                             public void onAnimationStarted() {
241                                 Intent intent =
242                                         new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
243                                 intent.setPackage("com.android.systemui");
244                                 sendBroadcastSafely(intent);
245                             }
246                         });
247                 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
248                 startActivitySafely(intent, opts.toBundle());
249             }
250         } catch (ActivityNotFoundException e) {
251             Log.e(TAG, "Failed to launch RecentAppsIntent", e);
252         }
253     }
254 
255     @Override
onConfigurationChanged(Configuration newConfig)256     protected void onConfigurationChanged(Configuration newConfig) {
257         if (mUseAlternateRecents) {
258             sAlternateRecents.onConfigurationChanged(newConfig);
259         }
260     }
261 
262     @Override
preloadRecents()263     public void preloadRecents() {
264         if (mUseAlternateRecents) {
265             sAlternateRecents.onPreloadRecents();
266         } else {
267             Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
268             intent.setClassName("com.android.systemui",
269                     "com.android.systemui.recent.RecentsPreloadReceiver");
270             sendBroadcastSafely(intent);
271 
272             RecentTasksLoader.getInstance(mContext).preloadFirstTask();
273         }
274     }
275 
276     @Override
cancelPreloadingRecents()277     public void cancelPreloadingRecents() {
278         if (mUseAlternateRecents) {
279             sAlternateRecents.onCancelPreloadingRecents();
280         } else {
281             Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
282             intent.setClassName("com.android.systemui",
283                     "com.android.systemui.recent.RecentsPreloadReceiver");
284             sendBroadcastSafely(intent);
285 
286             RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
287         }
288     }
289 
290     @Override
showNextAffiliatedTask()291     public void showNextAffiliatedTask() {
292         if (mUseAlternateRecents) {
293             sAlternateRecents.onShowNextAffiliatedTask();
294         }
295     }
296 
297     @Override
showPrevAffiliatedTask()298     public void showPrevAffiliatedTask() {
299         if (mUseAlternateRecents) {
300             sAlternateRecents.onShowPrevAffiliatedTask();
301         }
302     }
303 
304     @Override
setCallback(Callbacks cb)305     public void setCallback(Callbacks cb) {
306         if (mUseAlternateRecents) {
307             sAlternateRecents.setRecentsComponentCallback(cb);
308         }
309     }
310 
311     /**
312      * Send broadcast only if BOOT_COMPLETED
313      */
sendBroadcastSafely(Intent intent)314     private void sendBroadcastSafely(Intent intent) {
315         if (!mBootCompleted) return;
316         mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
317     }
318 
319     /**
320      * Start activity only if BOOT_COMPLETED
321      */
startActivitySafely(Intent intent, Bundle opts)322     private void startActivitySafely(Intent intent, Bundle opts) {
323         if (!mBootCompleted) return;
324         mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT));
325     }
326 }
327