RecentApplicationsDialog.java revision 8c769cb9cc02fe0496c16b51bc555729accf70c4
1/* 2 * Copyright (C) 2008 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.internal.policy.impl; 18 19import android.app.ActivityManager; 20import android.app.Dialog; 21import android.app.StatusBarManager; 22import android.content.ActivityNotFoundException; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.ActivityInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.os.Handler; 33import android.util.Log; 34import android.view.KeyEvent; 35import android.view.SoundEffectConstants; 36import android.view.View; 37import android.view.Window; 38import android.view.WindowManager; 39import android.view.View.OnClickListener; 40import android.widget.TextView; 41 42import java.util.List; 43 44public class RecentApplicationsDialog extends Dialog implements OnClickListener { 45 // Elements for debugging support 46// private static final String LOG_TAG = "RecentApplicationsDialog"; 47 private static final boolean DBG_FORCE_EMPTY_LIST = false; 48 49 static private StatusBarManager sStatusBar; 50 51 private static final int NUM_BUTTONS = 8; 52 private static final int MAX_RECENT_TASKS = NUM_BUTTONS * 2; // allow for some discards 53 54 final TextView[] mIcons = new TextView[NUM_BUTTONS]; 55 View mNoAppsText; 56 IntentFilter mBroadcastIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 57 58 class RecentTag { 59 ActivityManager.RecentTaskInfo info; 60 Intent intent; 61 } 62 63 Handler mHandler = new Handler(); 64 Runnable mCleanup = new Runnable() { 65 public void run() { 66 // dump extra memory we're hanging on to 67 for (TextView icon: mIcons) { 68 icon.setCompoundDrawables(null, null, null, null); 69 icon.setTag(null); 70 } 71 } 72 }; 73 74 private int mInitialModifiers; 75 76 public RecentApplicationsDialog(Context context, int initialModifiers) { 77 super(context, com.android.internal.R.style.Theme_Dialog_RecentApplications); 78 79 mInitialModifiers = initialModifiers; 80 } 81 82 /** 83 * We create the recent applications dialog just once, and it stays around (hidden) 84 * until activated by the user. 85 * 86 * @see PhoneWindowManager#showRecentAppsDialog 87 */ 88 @Override 89 protected void onCreate(Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 92 Context context = getContext(); 93 94 if (sStatusBar == null) { 95 sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE); 96 } 97 98 Window window = getWindow(); 99 window.requestFeature(Window.FEATURE_NO_TITLE); 100 window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 101 window.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 102 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 103 window.setTitle("Recents"); 104 105 setContentView(com.android.internal.R.layout.recent_apps_dialog); 106 107 final WindowManager.LayoutParams params = window.getAttributes(); 108 params.width = WindowManager.LayoutParams.MATCH_PARENT; 109 params.height = WindowManager.LayoutParams.MATCH_PARENT; 110 window.setAttributes(params); 111 window.setFlags(0, WindowManager.LayoutParams.FLAG_DIM_BEHIND); 112 113 mIcons[0] = (TextView)findViewById(com.android.internal.R.id.button0); 114 mIcons[1] = (TextView)findViewById(com.android.internal.R.id.button1); 115 mIcons[2] = (TextView)findViewById(com.android.internal.R.id.button2); 116 mIcons[3] = (TextView)findViewById(com.android.internal.R.id.button3); 117 mIcons[4] = (TextView)findViewById(com.android.internal.R.id.button4); 118 mIcons[5] = (TextView)findViewById(com.android.internal.R.id.button5); 119 mIcons[6] = (TextView)findViewById(com.android.internal.R.id.button6); 120 mIcons[7] = (TextView)findViewById(com.android.internal.R.id.button7); 121 mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message); 122 123 for (TextView b: mIcons) { 124 b.setOnClickListener(this); 125 } 126 } 127 128 @Override 129 public boolean onKeyDown(int keyCode, KeyEvent event) { 130 if (keyCode == KeyEvent.KEYCODE_APP_SWITCH || keyCode == KeyEvent.KEYCODE_TAB) { 131 // Ignore all meta keys other than SHIFT. The app switch key could be a 132 // fallback action chorded with ALT, META or even CTRL depending on the key map. 133 // DPad navigation is handled by the ViewRoot elsewhere. 134 final boolean backward = event.isShiftPressed(); 135 final int numIcons = mIcons.length; 136 int numButtons = 0; 137 while (numButtons < numIcons && mIcons[numButtons].getVisibility() == View.VISIBLE) { 138 numButtons += 1; 139 } 140 if (numButtons != 0) { 141 int nextFocus = backward ? numButtons - 1 : 0; 142 for (int i = 0; i < numButtons; i++) { 143 if (mIcons[i].hasFocus()) { 144 if (backward) { 145 nextFocus = (i + numButtons - 1) % numButtons; 146 } else { 147 nextFocus = (i + 1) % numButtons; 148 } 149 break; 150 } 151 } 152 final int direction = backward ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD; 153 if (mIcons[nextFocus].requestFocus(direction)) { 154 mIcons[nextFocus].playSoundEffect( 155 SoundEffectConstants.getContantForFocusDirection(direction)); 156 } 157 } 158 159 // The dialog always handles the key to prevent the ViewRoot from 160 // performing the default navigation itself. 161 return true; 162 } 163 164 return super.onKeyDown(keyCode, event); 165 } 166 167 @Override 168 public boolean onKeyUp(int keyCode, KeyEvent event) { 169 if (mInitialModifiers != 0 && event.hasNoModifiers()) { 170 final int numIcons = mIcons.length; 171 RecentTag tag = null; 172 for (int i = 0; i < numIcons; i++) { 173 if (mIcons[i].getVisibility() != View.VISIBLE) { 174 break; 175 } 176 if (i == 0 || mIcons[i].hasFocus()) { 177 tag = (RecentTag) mIcons[i].getTag(); 178 if (mIcons[i].hasFocus()) { 179 break; 180 } 181 } 182 } 183 if (tag != null) { 184 switchTo(tag); 185 } 186 dismiss(); 187 return true; 188 } 189 190 return super.onKeyUp(keyCode, event); 191 } 192 193 /** 194 * Handler for user clicks. If a button was clicked, launch the corresponding activity. 195 */ 196 public void onClick(View v) { 197 for (TextView b: mIcons) { 198 if (b == v) { 199 RecentTag tag = (RecentTag)b.getTag(); 200 switchTo(tag); 201 break; 202 } 203 } 204 dismiss(); 205 } 206 207 private void switchTo(RecentTag tag) { 208 if (tag.info.id >= 0) { 209 // This is an active task; it should just go to the foreground. 210 final ActivityManager am = (ActivityManager) 211 getContext().getSystemService(Context.ACTIVITY_SERVICE); 212 am.moveTaskToFront(tag.info.id, ActivityManager.MOVE_TASK_WITH_HOME); 213 } else if (tag.intent != null) { 214 tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 215 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 216 try { 217 getContext().startActivity(tag.intent); 218 } catch (ActivityNotFoundException e) { 219 Log.w("Recent", "Unable to launch recent task", e); 220 } 221 } 222 } 223 224 /** 225 * Set up and show the recent activities dialog. 226 */ 227 @Override 228 public void onStart() { 229 super.onStart(); 230 reloadButtons(); 231 if (sStatusBar != null) { 232 sStatusBar.disable(StatusBarManager.DISABLE_EXPAND); 233 } 234 235 // receive broadcasts 236 getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter); 237 238 mHandler.removeCallbacks(mCleanup); 239 } 240 241 /** 242 * Dismiss the recent activities dialog. 243 */ 244 @Override 245 public void onStop() { 246 super.onStop(); 247 248 if (sStatusBar != null) { 249 sStatusBar.disable(StatusBarManager.DISABLE_NONE); 250 } 251 252 // stop receiving broadcasts 253 getContext().unregisterReceiver(mBroadcastReceiver); 254 255 mHandler.postDelayed(mCleanup, 100); 256 } 257 258 /** 259 * Reload the 6 buttons with recent activities 260 */ 261 private void reloadButtons() { 262 263 final Context context = getContext(); 264 final PackageManager pm = context.getPackageManager(); 265 final ActivityManager am = (ActivityManager) 266 context.getSystemService(Context.ACTIVITY_SERVICE); 267 final List<ActivityManager.RecentTaskInfo> recentTasks = 268 am.getRecentTasks(MAX_RECENT_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); 269 270 ActivityInfo homeInfo = 271 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) 272 .resolveActivityInfo(pm, 0); 273 274 IconUtilities iconUtilities = new IconUtilities(getContext()); 275 276 // Performance note: Our android performance guide says to prefer Iterator when 277 // using a List class, but because we know that getRecentTasks() always returns 278 // an ArrayList<>, we'll use a simple index instead. 279 int index = 0; 280 int numTasks = recentTasks.size(); 281 for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) { 282 final ActivityManager.RecentTaskInfo info = recentTasks.get(i); 283 284 // for debug purposes only, disallow first result to create empty lists 285 if (DBG_FORCE_EMPTY_LIST && (i == 0)) continue; 286 287 Intent intent = new Intent(info.baseIntent); 288 if (info.origActivity != null) { 289 intent.setComponent(info.origActivity); 290 } 291 292 // Skip the current home activity. 293 if (homeInfo != null) { 294 if (homeInfo.packageName.equals( 295 intent.getComponent().getPackageName()) 296 && homeInfo.name.equals( 297 intent.getComponent().getClassName())) { 298 continue; 299 } 300 } 301 302 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) 303 | Intent.FLAG_ACTIVITY_NEW_TASK); 304 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); 305 if (resolveInfo != null) { 306 final ActivityInfo activityInfo = resolveInfo.activityInfo; 307 final String title = activityInfo.loadLabel(pm).toString(); 308 Drawable icon = activityInfo.loadIcon(pm); 309 310 if (title != null && title.length() > 0 && icon != null) { 311 final TextView tv = mIcons[index]; 312 tv.setText(title); 313 icon = iconUtilities.createIconDrawable(icon); 314 tv.setCompoundDrawables(null, icon, null, null); 315 RecentTag tag = new RecentTag(); 316 tag.info = info; 317 tag.intent = intent; 318 tv.setTag(tag); 319 tv.setVisibility(View.VISIBLE); 320 tv.setPressed(false); 321 tv.clearFocus(); 322 ++index; 323 } 324 } 325 } 326 327 // handle the case of "no icons to show" 328 mNoAppsText.setVisibility((index == 0) ? View.VISIBLE : View.GONE); 329 330 // hide the rest 331 for (; index < NUM_BUTTONS; ++index) { 332 mIcons[index].setVisibility(View.GONE); 333 } 334 } 335 336 /** 337 * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that 338 * we should close ourselves immediately, in order to allow a higher-priority UI to take over 339 * (e.g. phone call received). 340 */ 341 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 342 @Override 343 public void onReceive(Context context, Intent intent) { 344 String action = intent.getAction(); 345 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 346 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 347 if (! PhoneWindowManager.SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) { 348 dismiss(); 349 } 350 } 351 } 352 }; 353} 354