Launcher.java revision df0353815c629fc678824b07a234b89a1ff94208
1 2/* 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.launcher2; 19 20import com.android.common.Search; 21import com.android.launcher.R; 22import com.android.launcher2.Workspace.ShrinkState; 23 24import android.animation.Animator; 25import android.animation.AnimatorListenerAdapter; 26import android.animation.AnimatorSet; 27import android.animation.ValueAnimator; 28import android.animation.ValueAnimator.AnimatorUpdateListener; 29import android.app.Activity; 30import android.app.AlertDialog; 31import android.app.Dialog; 32import android.app.SearchManager; 33import android.app.StatusBarManager; 34import android.appwidget.AppWidgetManager; 35import android.appwidget.AppWidgetProviderInfo; 36import android.content.ActivityNotFoundException; 37import android.content.BroadcastReceiver; 38import android.content.ClipData; 39import android.content.ClipDescription; 40import android.content.ComponentName; 41import android.content.ContentResolver; 42import android.content.Context; 43import android.content.DialogInterface; 44import android.content.Intent; 45import android.content.IntentFilter; 46import android.content.Intent.ShortcutIconResource; 47import android.content.pm.ActivityInfo; 48import android.content.pm.PackageManager; 49import android.content.pm.ResolveInfo; 50import android.content.pm.PackageManager.NameNotFoundException; 51import android.content.res.Configuration; 52import android.content.res.Resources; 53import android.content.res.TypedArray; 54import android.database.ContentObserver; 55import android.graphics.Bitmap; 56import android.graphics.Canvas; 57import android.graphics.Rect; 58import android.graphics.drawable.ColorDrawable; 59import android.graphics.drawable.Drawable; 60import android.net.Uri; 61import android.os.AsyncTask; 62import android.os.Bundle; 63import android.os.Environment; 64import android.os.Handler; 65import android.os.Message; 66import android.os.Parcelable; 67import android.os.SystemClock; 68import android.os.SystemProperties; 69import android.provider.LiveFolders; 70import android.provider.Settings; 71import android.speech.RecognizerIntent; 72import android.text.Selection; 73import android.text.SpannableStringBuilder; 74import android.text.TextUtils; 75import android.text.method.TextKeyListener; 76import android.util.Log; 77import android.view.Display; 78import android.view.HapticFeedbackConstants; 79import android.view.KeyEvent; 80import android.view.LayoutInflater; 81import android.view.Menu; 82import android.view.MenuItem; 83import android.view.MotionEvent; 84import android.view.Surface; 85import android.view.View; 86import android.view.ViewGroup; 87import android.view.WindowManager; 88import android.view.View.OnClickListener; 89import android.view.View.OnLongClickListener; 90import android.view.accessibility.AccessibilityEvent; 91import android.view.animation.DecelerateInterpolator; 92import android.view.inputmethod.InputMethodManager; 93import android.widget.Advanceable; 94import android.widget.EditText; 95import android.widget.ImageView; 96import android.widget.LinearLayout; 97import android.widget.PopupWindow; 98import android.widget.TabHost; 99import android.widget.TextView; 100import android.widget.Toast; 101 102import java.io.DataInputStream; 103import java.io.DataOutputStream; 104import java.io.FileNotFoundException; 105import java.io.IOException; 106import java.util.ArrayList; 107import java.util.HashMap; 108import java.util.List; 109 110/** 111 * Default launcher application. 112 */ 113public final class Launcher extends Activity 114 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, 115 AllAppsView.Watcher, View.OnTouchListener { 116 static final String TAG = "Launcher"; 117 static final boolean LOGD = false; 118 119 static final boolean PROFILE_STARTUP = false; 120 static final boolean DEBUG_WIDGETS = false; 121 static final boolean DEBUG_USER_INTERFACE = false; 122 123 private static final int MENU_GROUP_ADD = 1; 124 private static final int MENU_GROUP_WALLPAPER = MENU_GROUP_ADD + 1; 125 126 private static final int MENU_ADD = Menu.FIRST + 1; 127 private static final int MENU_MANAGE_APPS = MENU_ADD + 1; 128 private static final int MENU_WALLPAPER_SETTINGS = MENU_MANAGE_APPS + 1; 129 private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1; 130 private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1; 131 private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1; 132 133 private static final int REQUEST_CREATE_SHORTCUT = 1; 134 private static final int REQUEST_CREATE_LIVE_FOLDER = 4; 135 private static final int REQUEST_CREATE_APPWIDGET = 5; 136 private static final int REQUEST_PICK_APPLICATION = 6; 137 private static final int REQUEST_PICK_SHORTCUT = 7; 138 private static final int REQUEST_PICK_LIVE_FOLDER = 8; 139 private static final int REQUEST_PICK_APPWIDGET = 9; 140 private static final int REQUEST_PICK_WALLPAPER = 10; 141 142 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; 143 144 static final int SCREEN_COUNT = 5; 145 static final int DEFAULT_SCREEN = 2; 146 147 static final int DIALOG_CREATE_SHORTCUT = 1; 148 static final int DIALOG_RENAME_FOLDER = 2; 149 150 private static final String PREFERENCES = "launcher.preferences"; 151 152 // Type: int 153 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; 154 // Type: int 155 private static final String RUNTIME_STATE = "launcher.state"; 156 // Type: long 157 private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder"; 158 // Type: int 159 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; 160 // Type: int 161 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX"; 162 // Type: int 163 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY"; 164 // Type: boolean 165 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder"; 166 // Type: long 167 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; 168 169 private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; 170 171 /** The different states that Launcher can be in. */ 172 private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, 173 CUSTOMIZE_SPRING_LOADED, ALL_APPS_SPRING_LOADED }; 174 private State mState = State.WORKSPACE; 175 private AnimatorSet mStateAnimation; 176 177 static final int APPWIDGET_HOST_ID = 1024; 178 179 private static final Object sLock = new Object(); 180 private static int sScreen = DEFAULT_SCREEN; 181 182 private final BroadcastReceiver mCloseSystemDialogsReceiver 183 = new CloseSystemDialogsIntentReceiver(); 184 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver(); 185 186 private LayoutInflater mInflater; 187 188 private DragController mDragController; 189 private Workspace mWorkspace; 190 191 private AppWidgetManager mAppWidgetManager; 192 private LauncherAppWidgetHost mAppWidgetHost; 193 194 private int mAddScreen = -1; 195 private int mAddIntersectCellX = -1; 196 private int mAddIntersectCellY = -1; 197 private int[] mAddDropPosition; 198 private int[] mTmpAddItemCellCoordinates = new int[2]; 199 200 private FolderInfo mFolderInfo; 201 202 private DeleteZone mDeleteZone; 203 private HandleView mHandleView; 204 private AllAppsView mAllAppsGrid; 205 private CustomizeTrayTabHost mHomeCustomizationDrawer; 206 private boolean mAutoAdvanceRunning = false; 207 208 private View mButtonCluster; 209 private View mAllAppsButton; 210 private View mDivider; 211 private View mConfigureButton; 212 213 private AllAppsPagedView mAllAppsPagedView = null; 214 private CustomizePagedView mCustomizePagedView = null; 215 216 private Bundle mSavedState; 217 218 private SpannableStringBuilder mDefaultKeySsb = null; 219 220 private boolean mWorkspaceLoading = true; 221 222 private boolean mPaused = true; 223 private boolean mRestoring; 224 private boolean mWaitingForResult; 225 private boolean mOnResumeNeedsLoad; 226 227 private Bundle mSavedInstanceState; 228 229 private LauncherModel mModel; 230 private IconCache mIconCache; 231 private boolean mUserPresent = true; 232 private boolean mVisible = false; 233 private boolean mAttached = false; 234 235 private static LocaleConfiguration sLocaleConfiguration = null; 236 237 private ArrayList<ItemInfo> mDesktopItems = new ArrayList<ItemInfo>(); 238 239 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>(); 240 241 // The "signpost" images along the bottom of the screen (only in some layouts) 242 private ImageView mPreviousView; 243 private ImageView mNextView; 244 245 // Hotseats (quick-launch icons next to AllApps) 246 private String[] mHotseatConfig = null; 247 private Intent[] mHotseats = null; 248 private Drawable[] mHotseatIcons = null; 249 private CharSequence[] mHotseatLabels = null; 250 251 private Intent mAppMarketIntent = null; 252 253 // Related to the auto-advancing of widgets 254 private final int ADVANCE_MSG = 1; 255 private final int mAdvanceInterval = 20000; 256 private final int mAdvanceStagger = 250; 257 private long mAutoAdvanceSentTime; 258 private long mAutoAdvanceTimeLeft = -1; 259 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = 260 new HashMap<View, AppWidgetProviderInfo>(); 261 262 // Determines how long to wait after a rotation before restoring the screen orientation to 263 // match the sensor state. 264 private final int mRestoreScreenOrientationDelay = 500; 265 266 // External icons saved in case of resource changes, orientation, etc. 267 private static Drawable.ConstantState sGlobalSearchIcon; 268 private static Drawable.ConstantState sVoiceSearchIcon; 269 private static Drawable.ConstantState sAppMarketIcon; 270 271 private BubbleTextView mWaitingForResume; 272 273 private static ArrayList<PendingAddArguments> sPendingAddList 274 = new ArrayList<PendingAddArguments>(); 275 276 private static class PendingAddArguments { 277 int requestCode; 278 Intent intent; 279 int screen; 280 int cellX; 281 int cellY; 282 } 283 284 @Override 285 protected void onCreate(Bundle savedInstanceState) { 286 super.onCreate(savedInstanceState); 287 288 if (LauncherApplication.isInPlaceRotationEnabled()) { 289 // hide the status bar (temporary until we get the status bar design figured out) 290 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 291 this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); 292 } 293 294 LauncherApplication app = ((LauncherApplication)getApplication()); 295 mModel = app.setLauncher(this); 296 mIconCache = app.getIconCache(); 297 mDragController = new DragController(this); 298 mInflater = getLayoutInflater(); 299 300 mAppWidgetManager = AppWidgetManager.getInstance(this); 301 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); 302 mAppWidgetHost.startListening(); 303 304 if (PROFILE_STARTUP) { 305 android.os.Debug.startMethodTracing( 306 Environment.getExternalStorageDirectory() + "/launcher"); 307 } 308 309 loadHotseats(); 310 checkForLocaleChange(); 311 setContentView(R.layout.launcher); 312 mHomeCustomizationDrawer = (CustomizeTrayTabHost) findViewById(R.id.customization_drawer); 313 if (mHomeCustomizationDrawer != null) { 314 // share the same customization workspace across all the tabs 315 mCustomizePagedView = (CustomizePagedView) findViewById( 316 R.id.customization_drawer_tab_contents); 317 } 318 setupViews(); 319 320 registerContentObservers(); 321 322 lockAllApps(); 323 324 mSavedState = savedInstanceState; 325 restoreState(mSavedState); 326 327 // Update customization drawer _after_ restoring the states 328 if (mCustomizePagedView != null) { 329 mCustomizePagedView.update(); 330 } 331 332 if (PROFILE_STARTUP) { 333 android.os.Debug.stopMethodTracing(); 334 } 335 336 if (!mRestoring) { 337 mModel.startLoader(this, true); 338 } 339 340 // For handling default keys 341 mDefaultKeySsb = new SpannableStringBuilder(); 342 Selection.setSelection(mDefaultKeySsb, 0); 343 344 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 345 registerReceiver(mCloseSystemDialogsReceiver, filter); 346 347 // If we have a saved version of these external icons, we load them up immediately 348 if (LauncherApplication.isScreenXLarge()) { 349 if (sGlobalSearchIcon == null || sVoiceSearchIcon == null || sAppMarketIcon == null) { 350 updateIconsAffectedByPackageManagerChanges(); 351 } 352 if (sGlobalSearchIcon != null) { 353 updateGlobalSearchIcon(sGlobalSearchIcon); 354 } 355 if (sVoiceSearchIcon != null) { 356 updateVoiceSearchIcon(sVoiceSearchIcon); 357 } 358 if (sAppMarketIcon != null) { 359 updateAppMarketIcon(sAppMarketIcon); 360 } 361 } 362 } 363 364 @Override 365 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 366 super.dispatchPopulateAccessibilityEvent(event); 367 368 // we want to take over text population so it is context dependent 369 event.getText().clear(); 370 if (mState == State.ALL_APPS) { 371 event.getText().add(getString(R.string.all_apps_button_label)); 372 } else if (mState == State.WORKSPACE) { 373 event.getText().add(getString(R.string.all_apps_home_button_label)); 374 } 375 376 return true; 377 } 378 379 private void checkForLocaleChange() { 380 if (sLocaleConfiguration == null) { 381 new AsyncTask<Void, Void, LocaleConfiguration>() { 382 @Override 383 protected LocaleConfiguration doInBackground(Void... unused) { 384 LocaleConfiguration localeConfiguration = new LocaleConfiguration(); 385 readConfiguration(Launcher.this, localeConfiguration); 386 return localeConfiguration; 387 } 388 389 @Override 390 protected void onPostExecute(LocaleConfiguration result) { 391 sLocaleConfiguration = result; 392 checkForLocaleChange(); // recursive, but now with a locale configuration 393 } 394 }.execute(); 395 return; 396 } 397 398 final Configuration configuration = getResources().getConfiguration(); 399 400 final String previousLocale = sLocaleConfiguration.locale; 401 final String locale = configuration.locale.toString(); 402 403 final int previousMcc = sLocaleConfiguration.mcc; 404 final int mcc = configuration.mcc; 405 406 final int previousMnc = sLocaleConfiguration.mnc; 407 final int mnc = configuration.mnc; 408 409 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; 410 411 if (localeChanged) { 412 sLocaleConfiguration.locale = locale; 413 sLocaleConfiguration.mcc = mcc; 414 sLocaleConfiguration.mnc = mnc; 415 416 mIconCache.flush(); 417 loadHotseats(); 418 419 final LocaleConfiguration localeConfiguration = sLocaleConfiguration; 420 new Thread("WriteLocaleConfiguration") { 421 @Override 422 public void run() { 423 writeConfiguration(Launcher.this, localeConfiguration); 424 } 425 }.start(); 426 } 427 } 428 429 private static class LocaleConfiguration { 430 public String locale; 431 public int mcc = -1; 432 public int mnc = -1; 433 } 434 435 private static void readConfiguration(Context context, LocaleConfiguration configuration) { 436 DataInputStream in = null; 437 try { 438 in = new DataInputStream(context.openFileInput(PREFERENCES)); 439 configuration.locale = in.readUTF(); 440 configuration.mcc = in.readInt(); 441 configuration.mnc = in.readInt(); 442 } catch (FileNotFoundException e) { 443 // Ignore 444 } catch (IOException e) { 445 // Ignore 446 } finally { 447 if (in != null) { 448 try { 449 in.close(); 450 } catch (IOException e) { 451 // Ignore 452 } 453 } 454 } 455 } 456 457 private static void writeConfiguration(Context context, LocaleConfiguration configuration) { 458 DataOutputStream out = null; 459 try { 460 out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE)); 461 out.writeUTF(configuration.locale); 462 out.writeInt(configuration.mcc); 463 out.writeInt(configuration.mnc); 464 out.flush(); 465 } catch (FileNotFoundException e) { 466 // Ignore 467 } catch (IOException e) { 468 //noinspection ResultOfMethodCallIgnored 469 context.getFileStreamPath(PREFERENCES).delete(); 470 } finally { 471 if (out != null) { 472 try { 473 out.close(); 474 } catch (IOException e) { 475 // Ignore 476 } 477 } 478 } 479 } 480 481 static int getScreen() { 482 synchronized (sLock) { 483 return sScreen; 484 } 485 } 486 487 static void setScreen(int screen) { 488 synchronized (sLock) { 489 sScreen = screen; 490 } 491 } 492 493 // Note: This doesn't do all the client-id magic that BrowserProvider does 494 // in Browser. (http://b/2425179) 495 private Uri getDefaultBrowserUri() { 496 String url = getString(R.string.default_browser_url); 497 if (url.indexOf("{CID}") != -1) { 498 url = url.replace("{CID}", "android-google"); 499 } 500 return Uri.parse(url); 501 } 502 503 // Load the Intent templates from arrays.xml to populate the hotseats. For 504 // each Intent, if it resolves to a single app, use that as the launch 505 // intent & use that app's label as the contentDescription. Otherwise, 506 // retain the ResolveActivity so the user can pick an app. 507 private void loadHotseats() { 508 if (mHotseatConfig == null) { 509 mHotseatConfig = getResources().getStringArray(R.array.hotseats); 510 if (mHotseatConfig.length > 0) { 511 mHotseats = new Intent[mHotseatConfig.length]; 512 mHotseatLabels = new CharSequence[mHotseatConfig.length]; 513 mHotseatIcons = new Drawable[mHotseatConfig.length]; 514 } else { 515 mHotseats = null; 516 mHotseatIcons = null; 517 mHotseatLabels = null; 518 } 519 520 TypedArray hotseatIconDrawables = getResources().obtainTypedArray(R.array.hotseat_icons); 521 for (int i=0; i<mHotseatConfig.length; i++) { 522 // load icon for this slot; currently unrelated to the actual activity 523 try { 524 mHotseatIcons[i] = hotseatIconDrawables.getDrawable(i); 525 } catch (ArrayIndexOutOfBoundsException ex) { 526 Log.w(TAG, "Missing hotseat_icons array item #" + i); 527 mHotseatIcons[i] = null; 528 } 529 } 530 hotseatIconDrawables.recycle(); 531 } 532 533 PackageManager pm = getPackageManager(); 534 for (int i=0; i<mHotseatConfig.length; i++) { 535 Intent intent = null; 536 if (mHotseatConfig[i].equals("*BROWSER*")) { 537 // magic value meaning "launch user's default web browser" 538 // replace it with a generic web request so we can see if there is indeed a default 539 String defaultUri = getString(R.string.default_browser_url); 540 intent = new Intent( 541 Intent.ACTION_VIEW, 542 ((defaultUri != null) 543 ? Uri.parse(defaultUri) 544 : getDefaultBrowserUri()) 545 ).addCategory(Intent.CATEGORY_BROWSABLE); 546 // note: if the user launches this without a default set, she 547 // will always be taken to the default URL above; this is 548 // unavoidable as we must specify a valid URL in order for the 549 // chooser to appear, and once the user selects something, that 550 // URL is unavoidably sent to the chosen app. 551 } else { 552 try { 553 intent = Intent.parseUri(mHotseatConfig[i], 0); 554 } catch (java.net.URISyntaxException ex) { 555 Log.w(TAG, "Invalid hotseat intent: " + mHotseatConfig[i]); 556 // bogus; leave intent=null 557 } 558 } 559 560 if (intent == null) { 561 mHotseats[i] = null; 562 mHotseatLabels[i] = getText(R.string.activity_not_found); 563 continue; 564 } 565 566 if (LOGD) { 567 Log.d(TAG, "loadHotseats: hotseat " + i 568 + " initial intent=[" 569 + intent.toUri(Intent.URI_INTENT_SCHEME) 570 + "]"); 571 } 572 573 ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 574 List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); 575 if (LOGD) { 576 Log.d(TAG, "Best match for intent: " + bestMatch); 577 Log.d(TAG, "All matches: "); 578 for (ResolveInfo ri : allMatches) { 579 Log.d(TAG, " --> " + ri); 580 } 581 } 582 // did this resolve to a single app, or the resolver? 583 if (allMatches.size() == 0 || bestMatch == null) { 584 // can't find any activity to handle this. let's leave the 585 // intent as-is and let Launcher show a toast when it fails 586 // to launch. 587 mHotseats[i] = intent; 588 589 // set accessibility text to "Not installed" 590 mHotseatLabels[i] = getText(R.string.activity_not_found); 591 } else { 592 boolean found = false; 593 for (ResolveInfo ri : allMatches) { 594 if (bestMatch.activityInfo.name.equals(ri.activityInfo.name) 595 && bestMatch.activityInfo.applicationInfo.packageName 596 .equals(ri.activityInfo.applicationInfo.packageName)) { 597 found = true; 598 break; 599 } 600 } 601 602 if (!found) { 603 if (LOGD) Log.d(TAG, "Multiple options, no default yet"); 604 // the bestMatch is probably the ResolveActivity, meaning the 605 // user has not yet selected a default 606 // so: we'll keep the original intent for now 607 mHotseats[i] = intent; 608 609 // set the accessibility text to "Select shortcut" 610 mHotseatLabels[i] = getText(R.string.title_select_shortcut); 611 } else { 612 // we have an app! 613 // now reconstruct the intent to launch it through the front 614 // door 615 ComponentName com = new ComponentName( 616 bestMatch.activityInfo.applicationInfo.packageName, 617 bestMatch.activityInfo.name); 618 mHotseats[i] = new Intent(Intent.ACTION_MAIN).setComponent(com); 619 620 // load the app label for accessibility 621 mHotseatLabels[i] = bestMatch.activityInfo.loadLabel(pm); 622 } 623 } 624 625 if (LOGD) { 626 Log.d(TAG, "loadHotseats: hotseat " + i 627 + " final intent=[" 628 + ((mHotseats[i] == null) 629 ? "null" 630 : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME)) 631 + "] label=[" + mHotseatLabels[i] 632 + "]" 633 ); 634 } 635 } 636 } 637 638 private void completeAdd(PendingAddArguments args) { 639 switch (args.requestCode) { 640 case REQUEST_PICK_APPLICATION: 641 completeAddApplication(args.intent, args.screen, args.cellX, args.cellY); 642 break; 643 case REQUEST_PICK_SHORTCUT: 644 processShortcut(args.intent); 645 break; 646 case REQUEST_CREATE_SHORTCUT: 647 completeAddShortcut(args.intent, args.screen, args.cellX, args.cellY); 648 break; 649 case REQUEST_PICK_LIVE_FOLDER: 650 addLiveFolder(args.intent); 651 break; 652 case REQUEST_CREATE_LIVE_FOLDER: 653 completeAddLiveFolder(args.intent, args.screen, args.cellX, args.cellY); 654 break; 655 case REQUEST_PICK_APPWIDGET: 656 addAppWidgetFromPick(args.intent); 657 break; 658 case REQUEST_CREATE_APPWIDGET: 659 int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); 660 completeAddAppWidget(appWidgetId, args.screen); 661 break; 662 case REQUEST_PICK_WALLPAPER: 663 // We just wanted the activity result here so we can clear mWaitingForResult 664 break; 665 } 666 } 667 668 @Override 669 protected void onActivityResult(final int requestCode, int resultCode, final Intent data) { 670 mWaitingForResult = false; 671 672 // The pattern used here is that a user PICKs a specific application, 673 // which, depending on the target, might need to CREATE the actual target. 674 675 // For example, the user would PICK_SHORTCUT for "Music playlist", and we 676 // launch over to the Music app to actually CREATE_SHORTCUT. 677 678 if (resultCode == RESULT_OK && mAddScreen != -1) { 679 final PendingAddArguments args = new PendingAddArguments(); 680 args.requestCode = requestCode; 681 args.intent = data; 682 args.screen = mAddScreen; 683 args.cellX = mAddIntersectCellX; 684 args.cellY = mAddIntersectCellY; 685 686 // If the loader is still running, defer the add until it is done. 687 if (isWorkspaceLocked()) { 688 sPendingAddList.add(args); 689 } else { 690 completeAdd(args); 691 } 692 } else if ((requestCode == REQUEST_PICK_APPWIDGET || 693 requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED && 694 data != null) { 695 // Clean up the appWidgetId if we canceled 696 int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); 697 if (appWidgetId != -1) { 698 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 699 } 700 } 701 } 702 703 @Override 704 protected void onResume() { 705 super.onResume(); 706 mPaused = false; 707 if (mRestoring || mOnResumeNeedsLoad) { 708 mWorkspaceLoading = true; 709 mModel.startLoader(this, true); 710 mRestoring = false; 711 mOnResumeNeedsLoad = false; 712 } 713 if (mWaitingForResume != null) { 714 mWaitingForResume.setStayPressed(false); 715 } 716 // When we resume Launcher, a different Activity might be responsible for the app 717 // market intent, so refresh the icon 718 updateAppMarketIcon(); 719 } 720 721 @Override 722 protected void onPause() { 723 super.onPause(); 724 // Some launcher layouts don't have a previous and next view 725 if (mPreviousView != null) { 726 dismissPreview(mPreviousView); 727 } 728 if (mNextView != null) { 729 dismissPreview(mNextView); 730 } 731 mPaused = true; 732 mDragController.cancelDrag(); 733 } 734 735 @Override 736 public Object onRetainNonConfigurationInstance() { 737 // Flag the loader to stop early before switching 738 mModel.stopLoader(); 739 mAllAppsGrid.surrender(); 740 return Boolean.TRUE; 741 } 742 743 // We can't hide the IME if it was forced open. So don't bother 744 /* 745 @Override 746 public void onWindowFocusChanged(boolean hasFocus) { 747 super.onWindowFocusChanged(hasFocus); 748 749 if (hasFocus) { 750 final InputMethodManager inputManager = (InputMethodManager) 751 getSystemService(Context.INPUT_METHOD_SERVICE); 752 WindowManager.LayoutParams lp = getWindow().getAttributes(); 753 inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new 754 android.os.Handler()) { 755 protected void onReceiveResult(int resultCode, Bundle resultData) { 756 Log.d(TAG, "ResultReceiver got resultCode=" + resultCode); 757 } 758 }); 759 Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged"); 760 } 761 } 762 */ 763 764 private boolean acceptFilter() { 765 final InputMethodManager inputManager = (InputMethodManager) 766 getSystemService(Context.INPUT_METHOD_SERVICE); 767 return !inputManager.isFullscreenMode(); 768 } 769 770 @Override 771 public boolean onKeyDown(int keyCode, KeyEvent event) { 772 boolean handled = super.onKeyDown(keyCode, event); 773 if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) { 774 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, 775 keyCode, event); 776 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { 777 // something usable has been typed - start a search 778 // the typed text will be retrieved and cleared by 779 // showSearchDialog() 780 // If there are multiple keystrokes before the search dialog takes focus, 781 // onSearchRequested() will be called for every keystroke, 782 // but it is idempotent, so it's fine. 783 return onSearchRequested(); 784 } 785 } 786 787 // Eat the long press event so the keyboard doesn't come up. 788 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { 789 return true; 790 } 791 792 return handled; 793 } 794 795 private String getTypedText() { 796 return mDefaultKeySsb.toString(); 797 } 798 799 private void clearTypedText() { 800 mDefaultKeySsb.clear(); 801 mDefaultKeySsb.clearSpans(); 802 Selection.setSelection(mDefaultKeySsb, 0); 803 } 804 805 /** 806 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type 807 * State 808 */ 809 private static State intToState(int stateOrdinal) { 810 State state = State.WORKSPACE; 811 final State[] stateValues = State.values(); 812 for (int i = 0; i < stateValues.length; i++) { 813 if (stateValues[i].ordinal() == stateOrdinal) { 814 state = stateValues[i]; 815 break; 816 } 817 } 818 return state; 819 } 820 821 /** 822 * Restores the previous state, if it exists. 823 * 824 * @param savedState The previous state. 825 */ 826 private void restoreState(Bundle savedState) { 827 if (savedState == null) { 828 return; 829 } 830 831 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); 832 833 if (state == State.ALL_APPS) { 834 showAllApps(false); 835 } else if (state == State.CUSTOMIZE) { 836 showCustomizationDrawer(false); 837 } 838 839 final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); 840 if (currentScreen > -1) { 841 mWorkspace.setCurrentPage(currentScreen); 842 } 843 844 final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); 845 846 if (addScreen > -1) { 847 mAddScreen = addScreen; 848 mAddIntersectCellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); 849 mAddIntersectCellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); 850 mRestoring = true; 851 } 852 853 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); 854 if (renameFolder) { 855 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); 856 mFolderInfo = mModel.getFolderById(this, sFolders, id); 857 mRestoring = true; 858 } 859 860 // Restore the current AllApps drawer tab 861 if (mAllAppsGrid != null && mAllAppsGrid instanceof AllAppsTabbed) { 862 String curTab = savedState.getString("allapps_currentTab"); 863 if (curTab != null) { 864 AllAppsTabbed tabhost = (AllAppsTabbed) mAllAppsGrid; 865 tabhost.setCurrentTabByTag(curTab); 866 } 867 int curPage = savedState.getInt("allapps_currentPage", -1); 868 if (curPage > -1) { 869 mAllAppsPagedView.setRestorePage(curPage); 870 } 871 } 872 873 // Restore the current customization drawer tab 874 if (mHomeCustomizationDrawer != null) { 875 String curTab = savedState.getString("customize_currentTab"); 876 if (curTab != null) { 877 // We set this directly so that there is no delay before the tab is set 878 mCustomizePagedView.setCustomizationFilter( 879 mHomeCustomizationDrawer.getCustomizeFilterForTabTag(curTab)); 880 mHomeCustomizationDrawer.setCurrentTabByTag(curTab); 881 } 882 883 // Note: currently we do not restore the page for the customization tray because unlike 884 // AllApps, the page content can change drastically 885 } 886 } 887 888 /** 889 * Finds all the views we need and configure them properly. 890 */ 891 private void setupViews() { 892 final DragController dragController = mDragController; 893 894 DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer); 895 dragLayer.setDragController(dragController); 896 897 mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view); 898 mAllAppsGrid.setLauncher(this); 899 mAllAppsGrid.setDragController(dragController); 900 ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window. 901 // Manage focusability manually since this thing is always visible (in non-xlarge) 902 ((View) mAllAppsGrid).setFocusable(false); 903 904 if (LauncherApplication.isScreenXLarge()) { 905 // They need to be INVISIBLE initially so that they will be measured in the layout. 906 // Otherwise the animations are messed up when we show them for the first time. 907 ((View) mAllAppsGrid).setVisibility(View.INVISIBLE); 908 mHomeCustomizationDrawer.setVisibility(View.INVISIBLE); 909 } 910 911 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); 912 913 final Workspace workspace = mWorkspace; 914 workspace.setHapticFeedbackEnabled(false); 915 916 DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone); 917 mDeleteZone = deleteZone; 918 919 View handleView = findViewById(R.id.all_apps_button); 920 if (handleView != null && handleView instanceof HandleView) { 921 // we don't use handle view in xlarge mode 922 mHandleView = (HandleView)handleView; 923 mHandleView.setLauncher(this); 924 mHandleView.setOnClickListener(this); 925 mHandleView.setOnLongClickListener(this); 926 } 927 928 if (mCustomizePagedView != null) { 929 mCustomizePagedView.setLauncher(this); 930 mCustomizePagedView.setDragController(dragController); 931 } else { 932 ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left); 933 hotseatLeft.setContentDescription(mHotseatLabels[0]); 934 hotseatLeft.setImageDrawable(mHotseatIcons[0]); 935 ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right); 936 hotseatRight.setContentDescription(mHotseatLabels[1]); 937 hotseatRight.setImageDrawable(mHotseatIcons[1]); 938 939 mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen); 940 mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen); 941 942 Drawable previous = mPreviousView.getDrawable(); 943 Drawable next = mNextView.getDrawable(); 944 mWorkspace.setIndicators(previous, next); 945 946 mPreviousView.setHapticFeedbackEnabled(false); 947 mPreviousView.setOnLongClickListener(this); 948 mNextView.setHapticFeedbackEnabled(false); 949 mNextView.setOnLongClickListener(this); 950 } 951 952 workspace.setOnLongClickListener(this); 953 workspace.setDragController(dragController); 954 workspace.setLauncher(this); 955 workspace.setWallpaperDimension(); 956 957 deleteZone.setLauncher(this); 958 deleteZone.setDragController(dragController); 959 960 final View allAppsButton = findViewById(R.id.all_apps_button); 961 final View divider = findViewById(R.id.divider); 962 final View configureButton = findViewById(R.id.configure_button); 963 964 if (LauncherApplication.isScreenXLarge()) { 965 deleteZone.setOverlappingViews(new View[] { allAppsButton, divider, configureButton }); 966 } else { 967 deleteZone.setOverlappingView(findViewById(R.id.all_apps_button_cluster)); 968 } 969 dragController.addDragListener(deleteZone); 970 971 DeleteZone allAppsDeleteZone = (DeleteZone) findViewById(R.id.all_apps_delete_zone); 972 if (allAppsDeleteZone != null) { 973 allAppsDeleteZone.setLauncher(this); 974 allAppsDeleteZone.setDragController(dragController); 975 allAppsDeleteZone.setDragAndDropEnabled(false); 976 dragController.addDragListener(allAppsDeleteZone); 977 dragController.addDropTarget(allAppsDeleteZone); 978 } 979 980 ApplicationInfoDropTarget allAppsInfoTarget = (ApplicationInfoDropTarget) 981 findViewById(R.id.all_apps_info_target); 982 if (allAppsInfoTarget != null) { 983 allAppsInfoTarget.setLauncher(this); 984 dragController.addDragListener(allAppsInfoTarget); 985 allAppsInfoTarget.setDragAndDropEnabled(false); 986 View marketButton = findViewById(R.id.market_button); 987 if (marketButton != null) { 988 allAppsInfoTarget.setOverlappingView(marketButton); 989 marketButton.setOnClickListener(new OnClickListener() { 990 public void onClick(View v) { 991 onClickAppMarketButton(v); 992 } 993 }); 994 } 995 } 996 997 dragController.setDragScoller(workspace); 998 dragController.setScrollView(dragLayer); 999 dragController.setMoveTarget(workspace); 1000 1001 // The order here is bottom to top. 1002 dragController.addDropTarget(workspace); 1003 dragController.addDropTarget(deleteZone); 1004 if (allAppsInfoTarget != null) { 1005 dragController.addDropTarget(allAppsInfoTarget); 1006 } 1007 if (allAppsDeleteZone != null) { 1008 dragController.addDropTarget(allAppsDeleteZone); 1009 } 1010 mButtonCluster = findViewById(R.id.all_apps_button_cluster); 1011 1012 mAllAppsButton = findViewById(R.id.all_apps_button); 1013 mDivider = findViewById(R.id.divider); 1014 mConfigureButton = findViewById(R.id.configure_button); 1015 1016 // We had previously set these click handlers in XML, but the first time we launched 1017 // Configure or All Apps we had an extra 50ms of delay while the java reflection methods 1018 // found the right handler. Setting the handlers directly here eliminates that cost. 1019 if (mConfigureButton != null) { 1020 mConfigureButton.setOnClickListener(new OnClickListener() { 1021 public void onClick(View v) { 1022 onClickConfigureButton(v); 1023 } 1024 }); 1025 } 1026 if (mDivider != null) { 1027 mDivider.setOnClickListener(new OnClickListener() { 1028 public void onClick(View v) { 1029 onClickAllAppsButton(v); 1030 } 1031 }); 1032 } 1033 if (mAllAppsButton != null) { 1034 mAllAppsButton.setOnClickListener(new OnClickListener() { 1035 public void onClick(View v) { 1036 onClickAllAppsButton(v); 1037 } 1038 }); 1039 } 1040 } 1041 1042 @SuppressWarnings({"UnusedDeclaration"}) 1043 public void previousScreen(View v) { 1044 if (mState != State.ALL_APPS) { 1045 mWorkspace.scrollLeft(); 1046 } 1047 } 1048 1049 @SuppressWarnings({"UnusedDeclaration"}) 1050 public void nextScreen(View v) { 1051 if (mState != State.ALL_APPS) { 1052 mWorkspace.scrollRight(); 1053 } 1054 } 1055 1056 @SuppressWarnings({"UnusedDeclaration"}) 1057 public void launchHotSeat(View v) { 1058 if (mState == State.ALL_APPS) return; 1059 1060 int index = -1; 1061 if (v.getId() == R.id.hotseat_left) { 1062 index = 0; 1063 } else if (v.getId() == R.id.hotseat_right) { 1064 index = 1; 1065 } 1066 1067 // reload these every tap; you never know when they might change 1068 loadHotseats(); 1069 if (index >= 0 && index < mHotseats.length && mHotseats[index] != null) { 1070 startActivitySafely( 1071 mHotseats[index], 1072 "hotseat" 1073 ); 1074 } 1075 } 1076 1077 /** 1078 * Creates a view representing a shortcut. 1079 * 1080 * @param info The data structure describing the shortcut. 1081 * 1082 * @return A View inflated from R.layout.application. 1083 */ 1084 View createShortcut(ShortcutInfo info) { 1085 return createShortcut(R.layout.application, 1086 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); 1087 } 1088 1089 /** 1090 * Creates a view representing a shortcut inflated from the specified resource. 1091 * 1092 * @param layoutResId The id of the XML layout used to create the shortcut. 1093 * @param parent The group the shortcut belongs to. 1094 * @param info The data structure describing the shortcut. 1095 * 1096 * @return A View inflated from layoutResId. 1097 */ 1098 View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { 1099 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false); 1100 favorite.applyFromShortcutInfo(info, mIconCache); 1101 favorite.setOnClickListener(this); 1102 return favorite; 1103 } 1104 1105 /** 1106 * Add an application shortcut to the workspace. 1107 * 1108 * @param data The intent describing the application. 1109 * @param cellInfo The position on screen where to create the shortcut. 1110 */ 1111 void completeAddApplication(Intent data, int screen, 1112 int intersectCellX, int intersectCellY) { 1113 final int[] cellXY = mTmpAddItemCellCoordinates; 1114 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); 1115 1116 if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { 1117 showOutOfSpaceMessage(); 1118 return; 1119 } 1120 1121 final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this); 1122 1123 if (info != null) { 1124 info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK | 1125 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1126 info.container = ItemInfo.NO_ID; 1127 mWorkspace.addApplicationShortcut(info, screen, cellXY[0], cellXY[1], 1128 isWorkspaceLocked(), mAddIntersectCellX, mAddIntersectCellY); 1129 } else { 1130 Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data); 1131 } 1132 } 1133 1134 /** 1135 * Add a shortcut to the workspace. 1136 * 1137 * @param data The intent describing the shortcut. 1138 * @param cellInfo The position on screen where to create the shortcut. 1139 */ 1140 private void completeAddShortcut(Intent data, int screen, 1141 int intersectCellX, int intersectCellY) { 1142 final int[] cellXY = mTmpAddItemCellCoordinates; 1143 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); 1144 1145 int[] touchXY = mAddDropPosition; 1146 boolean foundCellSpan = false; 1147 if (touchXY != null) { 1148 // when dragging and dropping, just find the closest free spot 1149 CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen); 1150 int[] result = screenLayout.findNearestVacantArea( 1151 touchXY[0], touchXY[1], 1, 1, cellXY); 1152 foundCellSpan = (result != null); 1153 } else { 1154 foundCellSpan = layout.findCellForSpanThatIntersects( 1155 cellXY, 1, 1, intersectCellX, intersectCellY); 1156 } 1157 1158 if (!foundCellSpan) { 1159 showOutOfSpaceMessage(); 1160 return; 1161 } 1162 1163 final ShortcutInfo info = mModel.addShortcut( 1164 this, data, screen, cellXY[0], cellXY[1], false); 1165 1166 if (!mRestoring) { 1167 final View view = createShortcut(info); 1168 mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); 1169 } 1170 } 1171 1172 /** 1173 * Add a widget to the workspace. 1174 * 1175 * @param appWidgetId The app widget id 1176 * @param cellInfo The position on screen where to create the widget. 1177 */ 1178 private void completeAddAppWidget(final int appWidgetId, int screen) { 1179 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); 1180 1181 // Calculate the grid spans needed to fit this widget 1182 CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); 1183 int[] spanXY = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight, null); 1184 1185 // Try finding open space on Launcher screen 1186 // We have saved the position to which the widget was dragged-- this really only matters 1187 // if we are placing widgets on a "spring-loaded" screen 1188 final int[] cellXY = mTmpAddItemCellCoordinates; 1189 1190 int[] touchXY = mAddDropPosition; 1191 boolean foundCellSpan = false; 1192 if (touchXY != null) { 1193 // when dragging and dropping, just find the closest free spot 1194 CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen); 1195 int[] result = screenLayout.findNearestVacantArea( 1196 touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY); 1197 foundCellSpan = (result != null); 1198 } else { 1199 // if we long pressed on an empty cell to bring up a menu, 1200 // make sure we intersect the empty cell 1201 // if mAddIntersectCellX/Y are -1 (e.g. we used menu -> add) then 1202 // findCellForSpanThatIntersects will just ignore them 1203 foundCellSpan = layout.findCellForSpanThatIntersects(cellXY, spanXY[0], spanXY[1], 1204 mAddIntersectCellX, mAddIntersectCellY); 1205 } 1206 1207 if (!foundCellSpan) { 1208 if (appWidgetId != -1) { 1209 // Deleting an app widget ID is a void call but writes to disk before returning 1210 // to the caller... 1211 final LauncherAppWidgetHost appWidgetHost = mAppWidgetHost; 1212 new Thread("deleteAppWidgetId") { 1213 public void run() { 1214 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 1215 } 1216 }.start(); 1217 } 1218 showOutOfSpaceMessage(); 1219 return; 1220 } 1221 1222 // Build Launcher-specific widget info and save to database 1223 LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId); 1224 launcherInfo.spanX = spanXY[0]; 1225 launcherInfo.spanY = spanXY[1]; 1226 1227 LauncherModel.addItemToDatabase(this, launcherInfo, 1228 LauncherSettings.Favorites.CONTAINER_DESKTOP, 1229 screen, cellXY[0], cellXY[1], false); 1230 1231 if (!mRestoring) { 1232 mDesktopItems.add(launcherInfo); 1233 1234 // Perform actual inflation because we're live 1235 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 1236 1237 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); 1238 launcherInfo.hostView.setTag(launcherInfo); 1239 1240 mWorkspace.addInScreen(launcherInfo.hostView, screen, cellXY[0], cellXY[1], 1241 launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); 1242 1243 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo); 1244 } 1245 } 1246 1247 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 1248 @Override 1249 public void onReceive(Context context, Intent intent) { 1250 final String action = intent.getAction(); 1251 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 1252 mUserPresent = false; 1253 updateRunning(); 1254 1255 // Reset AllApps to it's initial state 1256 if (mAllAppsGrid != null) { 1257 mAllAppsGrid.reset(); 1258 } 1259 } else if (Intent.ACTION_USER_PRESENT.equals(action)) { 1260 mUserPresent = true; 1261 updateRunning(); 1262 } 1263 } 1264 }; 1265 1266 @Override 1267 public void onAttachedToWindow() { 1268 super.onAttachedToWindow(); 1269 1270 // Listen for broadcasts related to user-presence 1271 final IntentFilter filter = new IntentFilter(); 1272 filter.addAction(Intent.ACTION_SCREEN_OFF); 1273 filter.addAction(Intent.ACTION_USER_PRESENT); 1274 registerReceiver(mReceiver, filter); 1275 1276 mAttached = true; 1277 mVisible = true; 1278 } 1279 1280 @Override 1281 public void onDetachedFromWindow() { 1282 super.onDetachedFromWindow(); 1283 mVisible = false; 1284 1285 if (mAttached) { 1286 unregisterReceiver(mReceiver); 1287 mAttached = false; 1288 } 1289 updateRunning(); 1290 } 1291 1292 public void onWindowVisibilityChanged(int visibility) { 1293 mVisible = visibility == View.VISIBLE; 1294 updateRunning(); 1295 } 1296 1297 private void sendAdvanceMessage(long delay) { 1298 mHandler.removeMessages(ADVANCE_MSG); 1299 Message msg = mHandler.obtainMessage(ADVANCE_MSG); 1300 mHandler.sendMessageDelayed(msg, delay); 1301 mAutoAdvanceSentTime = System.currentTimeMillis(); 1302 } 1303 1304 private void updateRunning() { 1305 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty(); 1306 if (autoAdvanceRunning != mAutoAdvanceRunning) { 1307 mAutoAdvanceRunning = autoAdvanceRunning; 1308 if (autoAdvanceRunning) { 1309 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; 1310 sendAdvanceMessage(delay); 1311 } else { 1312 if (!mWidgetsToAdvance.isEmpty()) { 1313 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - 1314 (System.currentTimeMillis() - mAutoAdvanceSentTime)); 1315 } 1316 mHandler.removeMessages(ADVANCE_MSG); 1317 mHandler.removeMessages(0); // Remove messages sent using postDelayed() 1318 } 1319 } 1320 } 1321 1322 private final Handler mHandler = new Handler() { 1323 @Override 1324 public void handleMessage(Message msg) { 1325 if (msg.what == ADVANCE_MSG) { 1326 int i = 0; 1327 for (View key: mWidgetsToAdvance.keySet()) { 1328 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); 1329 final int delay = mAdvanceStagger * i; 1330 if (v instanceof Advanceable) { 1331 postDelayed(new Runnable() { 1332 public void run() { 1333 ((Advanceable) v).advance(); 1334 } 1335 }, delay); 1336 } 1337 i++; 1338 } 1339 sendAdvanceMessage(mAdvanceInterval); 1340 } 1341 } 1342 }; 1343 1344 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) { 1345 if (appWidgetInfo.autoAdvanceViewId == -1) return; 1346 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId); 1347 if (v instanceof Advanceable) { 1348 mWidgetsToAdvance.put(hostView, appWidgetInfo); 1349 ((Advanceable) v).fyiWillBeAdvancedByHostKThx(); 1350 updateRunning(); 1351 } 1352 } 1353 1354 void removeWidgetToAutoAdvance(View hostView) { 1355 if (mWidgetsToAdvance.containsKey(hostView)) { 1356 mWidgetsToAdvance.remove(hostView); 1357 updateRunning(); 1358 } 1359 } 1360 1361 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { 1362 mDesktopItems.remove(launcherInfo); 1363 removeWidgetToAutoAdvance(launcherInfo.hostView); 1364 launcherInfo.hostView = null; 1365 } 1366 1367 void showOutOfSpaceMessage() { 1368 Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); 1369 } 1370 1371 public LauncherAppWidgetHost getAppWidgetHost() { 1372 return mAppWidgetHost; 1373 } 1374 1375 public LauncherModel getModel() { 1376 return mModel; 1377 } 1378 1379 void closeSystemDialogs() { 1380 getWindow().closeAllPanels(); 1381 1382 try { 1383 dismissDialog(DIALOG_CREATE_SHORTCUT); 1384 // Unlock the workspace if the dialog was showing 1385 } catch (Exception e) { 1386 // An exception is thrown if the dialog is not visible, which is fine 1387 } 1388 1389 try { 1390 dismissDialog(DIALOG_RENAME_FOLDER); 1391 // Unlock the workspace if the dialog was showing 1392 } catch (Exception e) { 1393 // An exception is thrown if the dialog is not visible, which is fine 1394 } 1395 1396 // Whatever we were doing is hereby canceled. 1397 mWaitingForResult = false; 1398 } 1399 1400 @Override 1401 protected void onNewIntent(Intent intent) { 1402 super.onNewIntent(intent); 1403 1404 // Close the menu 1405 if (Intent.ACTION_MAIN.equals(intent.getAction())) { 1406 // also will cancel mWaitingForResult. 1407 closeSystemDialogs(); 1408 1409 boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) 1410 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); 1411 1412 // In all these cases, only animate if we're already on home 1413 1414 if (LauncherApplication.isScreenXLarge()) { 1415 mWorkspace.unshrink(alreadyOnHome); 1416 } 1417 1418 mWorkspace.exitWidgetResizeMode(); 1419 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive()) { 1420 mWorkspace.moveToDefaultScreen(true); 1421 } 1422 showWorkspace(alreadyOnHome); 1423 1424 final View v = getWindow().peekDecorView(); 1425 if (v != null && v.getWindowToken() != null) { 1426 InputMethodManager imm = (InputMethodManager)getSystemService( 1427 INPUT_METHOD_SERVICE); 1428 imm.hideSoftInputFromWindow(v.getWindowToken(), 0); 1429 } 1430 1431 // Reset AllApps to it's initial state 1432 if (mAllAppsGrid != null) { 1433 mAllAppsGrid.reset(); 1434 } 1435 } 1436 } 1437 1438 @Override 1439 protected void onRestoreInstanceState(Bundle savedInstanceState) { 1440 // Do not call super here 1441 mSavedInstanceState = savedInstanceState; 1442 } 1443 1444 @Override 1445 protected void onSaveInstanceState(Bundle outState) { 1446 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); 1447 1448 final ArrayList<Folder> folders = mWorkspace.getOpenFolders(); 1449 if (folders.size() > 0) { 1450 final int count = folders.size(); 1451 long[] ids = new long[count]; 1452 for (int i = 0; i < count; i++) { 1453 final FolderInfo info = folders.get(i).getInfo(); 1454 ids[i] = info.id; 1455 } 1456 outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids); 1457 } else { 1458 super.onSaveInstanceState(outState); 1459 } 1460 1461 outState.putInt(RUNTIME_STATE, mState.ordinal()); 1462 1463 if (mAddScreen > -1 && mWaitingForResult) { 1464 outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mAddScreen); 1465 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mAddIntersectCellX); 1466 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mAddIntersectCellY); 1467 } 1468 1469 if (mFolderInfo != null && mWaitingForResult) { 1470 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); 1471 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); 1472 } 1473 1474 // Save the current AllApps drawer tab 1475 if (mAllAppsGrid != null && mAllAppsGrid instanceof AllAppsTabbed) { 1476 AllAppsTabbed tabhost = (AllAppsTabbed) mAllAppsGrid; 1477 String currentTabTag = tabhost.getCurrentTabTag(); 1478 if (currentTabTag != null) { 1479 outState.putString("allapps_currentTab", currentTabTag); 1480 outState.putInt("allapps_currentPage", mAllAppsPagedView.getCurrentPage()); 1481 } 1482 } 1483 1484 // Save the current customization drawer tab 1485 if (mHomeCustomizationDrawer != null) { 1486 String currentTabTag = mHomeCustomizationDrawer.getCurrentTabTag(); 1487 if (currentTabTag != null) { 1488 outState.putString("customize_currentTab", currentTabTag); 1489 } 1490 } 1491 } 1492 1493 @Override 1494 public void onDestroy() { 1495 super.onDestroy(); 1496 1497 try { 1498 mAppWidgetHost.stopListening(); 1499 } catch (NullPointerException ex) { 1500 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); 1501 } 1502 mAppWidgetHost = null; 1503 1504 mWidgetsToAdvance.clear(); 1505 1506 TextKeyListener.getInstance().release(); 1507 1508 mModel.stopLoader(); 1509 1510 unbindDesktopItems(); 1511 1512 getContentResolver().unregisterContentObserver(mWidgetObserver); 1513 1514 // Some launcher layouts don't have a previous and next view 1515 if (mPreviousView != null) { 1516 dismissPreview(mPreviousView); 1517 } 1518 if (mNextView != null) { 1519 dismissPreview(mNextView); 1520 } 1521 1522 unregisterReceiver(mCloseSystemDialogsReceiver); 1523 1524 ((ViewGroup) mWorkspace.getParent()).removeAllViews(); 1525 mWorkspace.removeAllViews(); 1526 mWorkspace = null; 1527 mDragController = null; 1528 1529 ValueAnimator.clearAllAnimations(); 1530 } 1531 1532 @Override 1533 public void startActivityForResult(Intent intent, int requestCode) { 1534 if (requestCode >= 0) mWaitingForResult = true; 1535 super.startActivityForResult(intent, requestCode); 1536 } 1537 1538 @Override 1539 public void startSearch(String initialQuery, boolean selectInitialQuery, 1540 Bundle appSearchData, boolean globalSearch) { 1541 1542 showWorkspace(true); 1543 1544 if (initialQuery == null) { 1545 // Use any text typed in the launcher as the initial query 1546 initialQuery = getTypedText(); 1547 clearTypedText(); 1548 } 1549 if (appSearchData == null) { 1550 appSearchData = new Bundle(); 1551 appSearchData.putString(Search.SOURCE, "launcher-search"); 1552 } 1553 1554 final SearchManager searchManager = 1555 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 1556 searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), 1557 appSearchData, globalSearch); 1558 } 1559 1560 @Override 1561 public boolean onCreateOptionsMenu(Menu menu) { 1562 if (isWorkspaceLocked()) { 1563 return false; 1564 } 1565 1566 super.onCreateOptionsMenu(menu); 1567 1568 menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add) 1569 .setIcon(android.R.drawable.ic_menu_add) 1570 .setAlphabeticShortcut('A'); 1571 menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps) 1572 .setIcon(android.R.drawable.ic_menu_manage) 1573 .setAlphabeticShortcut('M'); 1574 menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper) 1575 .setIcon(android.R.drawable.ic_menu_gallery) 1576 .setAlphabeticShortcut('W'); 1577 menu.add(0, MENU_SEARCH, 0, R.string.menu_search) 1578 .setIcon(android.R.drawable.ic_search_category_default) 1579 .setAlphabeticShortcut(SearchManager.MENU_KEY); 1580 menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications) 1581 .setIcon(com.android.internal.R.drawable.ic_menu_notifications) 1582 .setAlphabeticShortcut('N'); 1583 1584 final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS); 1585 settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1586 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1587 1588 menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings) 1589 .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P') 1590 .setIntent(settings); 1591 1592 return true; 1593 } 1594 1595 @Override 1596 public boolean onPrepareOptionsMenu(Menu menu) { 1597 super.onPrepareOptionsMenu(menu); 1598 1599 // If all apps is animating, don't show the menu, because we don't know 1600 // which one to show. 1601 if (mAllAppsGrid.isAnimating()) { 1602 return false; 1603 } 1604 1605 // Only show the add and wallpaper options when we're not in all apps. 1606 boolean visible = !mAllAppsGrid.isVisible(); 1607 menu.setGroupVisible(MENU_GROUP_ADD, visible); 1608 menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible); 1609 1610 // Disable add if the workspace is full. 1611 if (visible) { 1612 CellLayout layout = (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage()); 1613 menu.setGroupEnabled(MENU_GROUP_ADD, layout.existsEmptyCell()); 1614 } 1615 1616 return true; 1617 } 1618 1619 @Override 1620 public boolean onOptionsItemSelected(MenuItem item) { 1621 switch (item.getItemId()) { 1622 case MENU_ADD: 1623 addItems(); 1624 return true; 1625 case MENU_MANAGE_APPS: 1626 manageApps(); 1627 return true; 1628 case MENU_WALLPAPER_SETTINGS: 1629 startWallpaper(); 1630 return true; 1631 case MENU_SEARCH: 1632 onSearchRequested(); 1633 return true; 1634 case MENU_NOTIFICATIONS: 1635 showNotifications(); 1636 return true; 1637 } 1638 1639 return super.onOptionsItemSelected(item); 1640 } 1641 1642 /** 1643 * Indicates that we want global search for this activity by setting the globalSearch 1644 * argument for {@link #startSearch} to true. 1645 */ 1646 1647 @Override 1648 public boolean onSearchRequested() { 1649 startSearch(null, false, null, true); 1650 return true; 1651 } 1652 1653 public boolean isWorkspaceLocked() { 1654 return mWorkspaceLoading || mWaitingForResult; 1655 } 1656 1657 // Is the workspace preview (brought up by long-pressing on a signpost icon) visible? 1658 private boolean isPreviewVisible() { 1659 return (mPreviousView != null && mPreviousView.getTag() != null) || 1660 (mNextView != null && mNextView.getTag() != null); 1661 } 1662 1663 private void addItems() { 1664 if (LauncherApplication.isScreenXLarge()) { 1665 // Animate the widget chooser up from the bottom of the screen 1666 if (mState != State.CUSTOMIZE) { 1667 showCustomizationDrawer(true); 1668 } 1669 } else { 1670 showWorkspace(true); 1671 showAddDialog(-1, -1); 1672 } 1673 } 1674 1675 private void resetAddInfo() { 1676 mAddScreen = -1; 1677 mAddIntersectCellX = -1; 1678 mAddIntersectCellY = -1; 1679 mAddDropPosition = null; 1680 } 1681 1682 void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) { 1683 resetAddInfo(); 1684 mAddScreen = screen; 1685 mAddDropPosition = position; 1686 1687 int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 1688 AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName); 1689 addAppWidgetImpl(appWidgetId, info); 1690 } 1691 1692 private void manageApps() { 1693 startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS)); 1694 } 1695 1696 void addAppWidgetFromPick(Intent data) { 1697 // TODO: catch bad widget exception when sent 1698 int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); 1699 // TODO: Is this log message meaningful? 1700 if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras()); 1701 addAppWidgetImpl(appWidgetId, null); 1702 } 1703 1704 void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) { 1705 AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId); 1706 1707 if (appWidget.configure != null) { 1708 // Launch over to configure widget, if needed 1709 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); 1710 intent.setComponent(appWidget.configure); 1711 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 1712 if (info != null) { 1713 if (info.mimeType != null && !info.mimeType.isEmpty()) { 1714 intent.putExtra( 1715 InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE, 1716 info.mimeType); 1717 1718 final String mimeType = info.mimeType; 1719 final ClipData clipData = (ClipData) info.configurationData; 1720 final ClipDescription clipDesc = clipData.getDescription(); 1721 for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) { 1722 if (clipDesc.getMimeType(i).equals(mimeType)) { 1723 final ClipData.Item item = clipData.getItemAt(i); 1724 final CharSequence stringData = item.getText(); 1725 final Uri uriData = item.getUri(); 1726 final Intent intentData = item.getIntent(); 1727 final String key = 1728 InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA; 1729 if (uriData != null) { 1730 intent.putExtra(key, uriData); 1731 } else if (intentData != null) { 1732 intent.putExtra(key, intentData); 1733 } else if (stringData != null) { 1734 intent.putExtra(key, stringData); 1735 } 1736 break; 1737 } 1738 } 1739 } 1740 } 1741 1742 startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); 1743 } else { 1744 // Otherwise just add it 1745 completeAddAppWidget(appWidgetId, mAddScreen); 1746 } 1747 } 1748 1749 void processShortcutFromDrop(ComponentName componentName, int screen, int[] position) { 1750 resetAddInfo(); 1751 mAddScreen = screen; 1752 mAddDropPosition = position; 1753 1754 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 1755 createShortcutIntent.setComponent(componentName); 1756 processShortcut(createShortcutIntent); 1757 } 1758 1759 void processShortcut(Intent intent) { 1760 // Handle case where user selected "Applications" 1761 String applicationName = getResources().getString(R.string.group_applications); 1762 String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 1763 1764 if (applicationName != null && applicationName.equals(shortcutName)) { 1765 Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 1766 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 1767 1768 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); 1769 pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); 1770 pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application)); 1771 startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION); 1772 } else { 1773 startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT); 1774 } 1775 } 1776 1777 void processWallpaper(Intent intent) { 1778 startActivityForResult(intent, REQUEST_PICK_WALLPAPER); 1779 } 1780 1781 void addLiveFolderFromDrop(ComponentName componentName, int screen, int[] position) { 1782 resetAddInfo(); 1783 mAddScreen = screen; 1784 mAddDropPosition = position; 1785 1786 Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER); 1787 createFolderIntent.setComponent(componentName); 1788 1789 addLiveFolder(createFolderIntent); 1790 } 1791 1792 void addLiveFolder(Intent intent) { // YYY add screen intersect etc. parameters here 1793 // Handle case where user selected "Folder" 1794 String folderName = getResources().getString(R.string.group_folder); 1795 String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 1796 1797 if (folderName != null && folderName.equals(shortcutName)) { 1798 addFolder(mAddScreen, mAddIntersectCellX, mAddIntersectCellY); 1799 } else { 1800 startActivityForResultSafely(intent, REQUEST_CREATE_LIVE_FOLDER); 1801 } 1802 } 1803 1804 FolderIcon addFolder(int screen, int intersectCellX, int intersectCellY) { 1805 UserFolderInfo folderInfo = new UserFolderInfo(); 1806 folderInfo.title = getText(R.string.folder_name); 1807 1808 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); 1809 final int[] cellXY = mTmpAddItemCellCoordinates; 1810 if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { 1811 showOutOfSpaceMessage(); 1812 return null; 1813 } 1814 1815 // Update the model 1816 LauncherModel.addItemToDatabase(this, folderInfo, 1817 LauncherSettings.Favorites.CONTAINER_DESKTOP, 1818 screen, cellXY[0], cellXY[1], false); 1819 sFolders.put(folderInfo.id, folderInfo); 1820 1821 // Create the view 1822 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, 1823 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), 1824 folderInfo, mIconCache); 1825 mWorkspace.addInScreen(newFolder, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); 1826 return newFolder; 1827 } 1828 1829 void removeFolder(FolderInfo folder) { 1830 sFolders.remove(folder.id); 1831 } 1832 1833 private void completeAddLiveFolder( 1834 Intent data, int screen, int intersectCellX, int intersectCellY) { 1835 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); 1836 final int[] cellXY = mTmpAddItemCellCoordinates; 1837 if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { 1838 showOutOfSpaceMessage(); 1839 return; 1840 } 1841 1842 final LiveFolderInfo info = addLiveFolder(this, data, screen, cellXY[0], cellXY[1], false); 1843 1844 if (!mRestoring) { 1845 final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this, 1846 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); 1847 mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); 1848 } 1849 } 1850 1851 static LiveFolderInfo addLiveFolder(Context context, Intent data, 1852 int screen, int cellX, int cellY, boolean notify) { 1853 1854 Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT); 1855 String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME); 1856 1857 Drawable icon = null; 1858 Intent.ShortcutIconResource iconResource = null; 1859 1860 Parcelable extra = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON); 1861 if (extra != null && extra instanceof Intent.ShortcutIconResource) { 1862 try { 1863 iconResource = (Intent.ShortcutIconResource) extra; 1864 final PackageManager packageManager = context.getPackageManager(); 1865 Resources resources = packageManager.getResourcesForApplication( 1866 iconResource.packageName); 1867 final int id = resources.getIdentifier(iconResource.resourceName, null, null); 1868 icon = resources.getDrawable(id); 1869 } catch (Exception e) { 1870 Log.w(TAG, "Could not load live folder icon: " + extra); 1871 } 1872 } 1873 1874 if (icon == null) { 1875 icon = context.getResources().getDrawable(R.drawable.ic_launcher_folder); 1876 } 1877 1878 final LiveFolderInfo info = new LiveFolderInfo(); 1879 info.icon = Utilities.createIconBitmap(icon, context); 1880 info.title = name; 1881 info.iconResource = iconResource; 1882 info.uri = data.getData(); 1883 info.baseIntent = baseIntent; 1884 info.displayMode = data.getIntExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, 1885 LiveFolders.DISPLAY_MODE_GRID); 1886 1887 LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, 1888 screen, cellX, cellY, notify); 1889 sFolders.put(info.id, info); 1890 1891 return info; 1892 } 1893 1894 private void showNotifications() { 1895 final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE); 1896 if (statusBar != null) { 1897 statusBar.expand(); 1898 } 1899 } 1900 1901 private void startWallpaper() { 1902 showWorkspace(true); 1903 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); 1904 Intent chooser = Intent.createChooser(pickWallpaper, 1905 getText(R.string.chooser_wallpaper)); 1906 // NOTE: Adds a configure option to the chooser if the wallpaper supports it 1907 // Removed in Eclair MR1 1908// WallpaperManager wm = (WallpaperManager) 1909// getSystemService(Context.WALLPAPER_SERVICE); 1910// WallpaperInfo wi = wm.getWallpaperInfo(); 1911// if (wi != null && wi.getSettingsActivity() != null) { 1912// LabeledIntent li = new LabeledIntent(getPackageName(), 1913// R.string.configure_wallpaper, 0); 1914// li.setClassName(wi.getPackageName(), wi.getSettingsActivity()); 1915// chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li }); 1916// } 1917 startActivityForResult(chooser, REQUEST_PICK_WALLPAPER); 1918 } 1919 1920 /** 1921 * Registers various content observers. The current implementation registers 1922 * only a favorites observer to keep track of the favorites applications. 1923 */ 1924 private void registerContentObservers() { 1925 ContentResolver resolver = getContentResolver(); 1926 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, 1927 true, mWidgetObserver); 1928 } 1929 1930 @Override 1931 public boolean dispatchKeyEvent(KeyEvent event) { 1932 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1933 switch (event.getKeyCode()) { 1934 case KeyEvent.KEYCODE_HOME: 1935 return true; 1936 case KeyEvent.KEYCODE_VOLUME_DOWN: 1937 if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) { 1938 dumpState(); 1939 return true; 1940 } 1941 break; 1942 } 1943 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1944 switch (event.getKeyCode()) { 1945 case KeyEvent.KEYCODE_HOME: 1946 return true; 1947 } 1948 } 1949 1950 return super.dispatchKeyEvent(event); 1951 } 1952 1953 @Override 1954 public void onBackPressed() { 1955 if (mState == State.ALL_APPS || mState == State.CUSTOMIZE) { 1956 showWorkspace(true); 1957 } else if (mWorkspace.getOpenFolder() != null) { 1958 closeFolder(); 1959 } else if (isPreviewVisible()) { 1960 dismissPreview(mPreviousView); 1961 dismissPreview(mNextView); 1962 } else { 1963 mWorkspace.exitWidgetResizeMode(); 1964 1965 // Back button is a no-op here, but give at least some feedback for the button press 1966 mWorkspace.showOutlinesTemporarily(); 1967 } 1968 } 1969 1970 private void closeFolder() { 1971 Folder folder = mWorkspace.getOpenFolder(); 1972 if (folder != null) { 1973 closeFolder(folder); 1974 } 1975 } 1976 1977 void closeFolder(Folder folder) { 1978 folder.getInfo().opened = false; 1979 ViewGroup parent = (ViewGroup) folder.getParent().getParent(); 1980 if (parent != null) { 1981 CellLayout cl = (CellLayout) parent; 1982 cl.removeViewWithoutMarkingCells(folder); 1983 if (folder instanceof DropTarget) { 1984 // Live folders aren't DropTargets. 1985 mDragController.removeDropTarget((DropTarget)folder); 1986 } 1987 } 1988 folder.onClose(); 1989 } 1990 1991 /** 1992 * Re-listen when widgets are reset. 1993 */ 1994 private void onAppWidgetReset() { 1995 if (mAppWidgetHost != null) { 1996 mAppWidgetHost.startListening(); 1997 } 1998 } 1999 2000 /** 2001 * Go through the and disconnect any of the callbacks in the drawables and the views or we 2002 * leak the previous Home screen on orientation change. 2003 */ 2004 private void unbindDesktopItems() { 2005 for (ItemInfo item: mDesktopItems) { 2006 item.unbind(); 2007 } 2008 mDesktopItems.clear(); 2009 } 2010 2011 /** 2012 * Launches the intent referred by the clicked shortcut. 2013 * 2014 * @param v The view representing the clicked shortcut. 2015 */ 2016 public void onClick(View v) { 2017 Object tag = v.getTag(); 2018 if (tag instanceof ShortcutInfo) { 2019 // Open shortcut 2020 final Intent intent = ((ShortcutInfo) tag).intent; 2021 int[] pos = new int[2]; 2022 v.getLocationOnScreen(pos); 2023 intent.setSourceBounds(new Rect(pos[0], pos[1], 2024 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 2025 boolean success = startActivitySafely(intent, tag); 2026 2027 if (success && v instanceof BubbleTextView) { 2028 mWaitingForResume = (BubbleTextView) v; 2029 mWaitingForResume.setStayPressed(true); 2030 } 2031 } else if (tag instanceof FolderInfo) { 2032 handleFolderClick((FolderInfo) tag); 2033 } else if (v == mHandleView) { 2034 if (mState == State.ALL_APPS) { 2035 showWorkspace(true); 2036 } else { 2037 showAllApps(true); 2038 } 2039 } 2040 } 2041 2042 public boolean onTouch(View v, MotionEvent event) { 2043 // this is an intercepted event being forwarded from mWorkspace; 2044 // clicking anywhere on the workspace causes the customization drawer to slide down 2045 showWorkspace(true); 2046 return false; 2047 } 2048 2049 /** 2050 * Event handler for the search button 2051 * 2052 * @param v The view that was clicked. 2053 */ 2054 public void onClickSearchButton(View v) { 2055 startSearch(null, false, null, true); 2056 // Use a custom animation for launching search 2057 overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast); 2058 } 2059 2060 /** 2061 * Event handler for the voice button 2062 * 2063 * @param v The view that was clicked. 2064 */ 2065 public void onClickVoiceButton(View v) { 2066 startVoiceSearch(); 2067 } 2068 2069 private void startVoiceSearch() { 2070 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 2071 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2072 startActivity(intent); 2073 } 2074 2075 /** 2076 * Event handler for the "gear" button that appears on the home screen, which 2077 * enters home screen customization mode. 2078 * 2079 * @param v The view that was clicked. 2080 */ 2081 public void onClickConfigureButton(View v) { 2082 addItems(); 2083 } 2084 2085 /** 2086 * Event handler for the "grid" button that appears on the home screen, which 2087 * enters all apps mode. 2088 * 2089 * @param v The view that was clicked. 2090 */ 2091 public void onClickAllAppsButton(View v) { 2092 showAllApps(true); 2093 } 2094 2095 public void onClickAppMarketButton(View v) { 2096 if (mAppMarketIntent != null) { 2097 startActivitySafely(mAppMarketIntent, "app market"); 2098 } 2099 } 2100 2101 void startApplicationDetailsActivity(ComponentName componentName) { 2102 String packageName = componentName.getPackageName(); 2103 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 2104 Uri.fromParts("package", packageName, null)); 2105 startActivity(intent); 2106 } 2107 2108 void startApplicationUninstallActivity(ApplicationInfo appInfo) { 2109 if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) { 2110 // System applications cannot be installed. For now, show a toast explaining that. 2111 // We may give them the option of disabling apps this way. 2112 int messageId = R.string.uninstall_system_app_text; 2113 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show(); 2114 } else { 2115 String packageName = appInfo.componentName.getPackageName(); 2116 String className = appInfo.componentName.getClassName(); 2117 Intent intent = new Intent( 2118 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className)); 2119 startActivity(intent); 2120 } 2121 } 2122 2123 boolean startActivitySafely(Intent intent, Object tag) { 2124 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2125 try { 2126 startActivity(intent); 2127 return true; 2128 } catch (ActivityNotFoundException e) { 2129 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2130 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); 2131 } catch (SecurityException e) { 2132 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2133 Log.e(TAG, "Launcher does not have the permission to launch " + intent + 2134 ". Make sure to create a MAIN intent-filter for the corresponding activity " + 2135 "or use the exported attribute for this activity. " 2136 + "tag="+ tag + " intent=" + intent, e); 2137 } 2138 return false; 2139 } 2140 2141 void startActivityForResultSafely(Intent intent, int requestCode) { 2142 try { 2143 startActivityForResult(intent, requestCode); 2144 } catch (ActivityNotFoundException e) { 2145 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2146 } catch (SecurityException e) { 2147 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2148 Log.e(TAG, "Launcher does not have the permission to launch " + intent + 2149 ". Make sure to create a MAIN intent-filter for the corresponding activity " + 2150 "or use the exported attribute for this activity.", e); 2151 } 2152 } 2153 2154 private void handleFolderClick(FolderInfo folderInfo) { 2155 if (!folderInfo.opened) { 2156 // Close any open folder 2157 closeFolder(); 2158 // Open the requested folder 2159 openFolder(folderInfo); 2160 } else { 2161 // Find the open folder... 2162 Folder openFolder = mWorkspace.getFolderForTag(folderInfo); 2163 int folderScreen; 2164 if (openFolder != null) { 2165 folderScreen = mWorkspace.getPageForView(openFolder); 2166 // .. and close it 2167 closeFolder(openFolder); 2168 if (folderScreen != mWorkspace.getCurrentPage()) { 2169 // Close any folder open on the current screen 2170 closeFolder(); 2171 // Pull the folder onto this screen 2172 openFolder(folderInfo); 2173 } 2174 } 2175 } 2176 } 2177 2178 /** 2179 * Opens the user folder described by the specified tag. The opening of the folder 2180 * is animated relative to the specified View. If the View is null, no animation 2181 * is played. 2182 * 2183 * @param folderInfo The FolderInfo describing the folder to open. 2184 */ 2185 public void openFolder(FolderInfo folderInfo) { 2186 Folder openFolder; 2187 2188 if (folderInfo instanceof UserFolderInfo) { 2189 openFolder = UserFolder.fromXml(this); 2190 } else if (folderInfo instanceof LiveFolderInfo) { 2191 openFolder = com.android.launcher2.LiveFolder.fromXml(this, folderInfo); 2192 } else { 2193 return; 2194 } 2195 2196 openFolder.setDragController(mDragController); 2197 openFolder.setLauncher(this); 2198 2199 openFolder.bind(folderInfo); 2200 folderInfo.opened = true; 2201 2202 mWorkspace.addInFullScreen(openFolder, folderInfo.screen); 2203 2204 openFolder.onOpen(); 2205 } 2206 2207 public boolean onLongClick(View v) { 2208 if (mState != State.WORKSPACE) { 2209 return false; 2210 } 2211 2212 switch (v.getId()) { 2213 case R.id.previous_screen: 2214 if (mState != State.ALL_APPS) { 2215 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2216 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2217 showPreviews(v); 2218 } 2219 return true; 2220 case R.id.next_screen: 2221 if (mState != State.ALL_APPS) { 2222 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2223 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2224 showPreviews(v); 2225 } 2226 return true; 2227 case R.id.all_apps_button: 2228 if (mState != State.ALL_APPS) { 2229 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2230 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2231 showPreviews(v); 2232 } 2233 return true; 2234 } 2235 2236 if (isWorkspaceLocked()) { 2237 return false; 2238 } 2239 2240 if (!(v instanceof CellLayout)) { 2241 v = (View) v.getParent().getParent(); 2242 } 2243 2244 resetAddInfo(); 2245 CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); 2246 // This happens when long clicking an item with the dpad/trackball 2247 if (longClickCellInfo == null || !longClickCellInfo.valid) { 2248 return true; 2249 } 2250 2251 final View itemUnderLongClick = longClickCellInfo.cell; 2252 2253 if (mWorkspace.allowLongPress() && !mDragController.isDragging()) { 2254 if (itemUnderLongClick == null) { 2255 // User long pressed on empty space 2256 mWorkspace.setAllowLongPress(false); 2257 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2258 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2259 if (LauncherApplication.isScreenXLarge()) { 2260 addItems(); 2261 } else { 2262 showAddDialog(longClickCellInfo.cellX, longClickCellInfo.cellY); 2263 } 2264 } else { 2265 if (!(itemUnderLongClick instanceof Folder)) { 2266 // User long pressed on an item 2267 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2268 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2269 mAddIntersectCellX = longClickCellInfo.cellX; 2270 mAddIntersectCellY = longClickCellInfo.cellY; 2271 mWorkspace.startDrag(longClickCellInfo); 2272 } 2273 } 2274 } 2275 return true; 2276 } 2277 2278 @SuppressWarnings({"unchecked"}) 2279 private void dismissPreview(final View v) { 2280 final PopupWindow window = (PopupWindow) v.getTag(); 2281 if (window != null) { 2282 window.setOnDismissListener(new PopupWindow.OnDismissListener() { 2283 public void onDismiss() { 2284 ViewGroup group = (ViewGroup) v.getTag(R.id.workspace); 2285 int count = group.getChildCount(); 2286 for (int i = 0; i < count; i++) { 2287 ((ImageView) group.getChildAt(i)).setImageDrawable(null); 2288 } 2289 ArrayList<Bitmap> bitmaps = (ArrayList<Bitmap>) v.getTag(R.id.icon); 2290 for (Bitmap bitmap : bitmaps) bitmap.recycle(); 2291 2292 v.setTag(R.id.workspace, null); 2293 v.setTag(R.id.icon, null); 2294 window.setOnDismissListener(null); 2295 } 2296 }); 2297 window.dismiss(); 2298 } 2299 v.setTag(null); 2300 } 2301 2302 private void showPreviews(View anchor) { 2303 showPreviews(anchor, 0, mWorkspace.getChildCount()); 2304 } 2305 2306 private void showPreviews(final View anchor, int start, int end) { 2307 final Resources resources = getResources(); 2308 final Workspace workspace = mWorkspace; 2309 2310 CellLayout cell = ((CellLayout) workspace.getChildAt(start)); 2311 2312 float max = workspace.getChildCount(); 2313 2314 final Rect r = new Rect(); 2315 resources.getDrawable(R.drawable.preview_background).getPadding(r); 2316 int extraW = (int) ((r.left + r.right) * max); 2317 int extraH = r.top + r.bottom; 2318 2319 int aW = cell.getWidth() - extraW; 2320 float w = aW / max; 2321 2322 int width = cell.getWidth(); 2323 int height = cell.getHeight(); 2324 int x = cell.getLeftPadding(); 2325 int y = cell.getTopPadding(); 2326 width -= (x + cell.getRightPadding()); 2327 height -= (y + cell.getBottomPadding()); 2328 2329 float scale = w / width; 2330 2331 int count = end - start; 2332 2333 final float sWidth = width * scale; 2334 float sHeight = height * scale; 2335 2336 LinearLayout preview = new LinearLayout(this); 2337 2338 PreviewTouchHandler handler = new PreviewTouchHandler(anchor); 2339 ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(count); 2340 2341 for (int i = start; i < end; i++) { 2342 ImageView image = new ImageView(this); 2343 cell = (CellLayout) workspace.getChildAt(i); 2344 2345 final Bitmap bitmap = Bitmap.createBitmap((int) sWidth, (int) sHeight, 2346 Bitmap.Config.ARGB_8888); 2347 2348 final Canvas c = new Canvas(bitmap); 2349 c.scale(scale, scale); 2350 c.translate(-cell.getLeftPadding(), -cell.getTopPadding()); 2351 cell.drawChildren(c); 2352 2353 image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background)); 2354 image.setImageBitmap(bitmap); 2355 image.setTag(i); 2356 image.setOnClickListener(handler); 2357 image.setOnFocusChangeListener(handler); 2358 image.setFocusable(true); 2359 if (i == mWorkspace.getCurrentPage()) image.requestFocus(); 2360 2361 preview.addView(image, 2362 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 2363 2364 bitmaps.add(bitmap); 2365 } 2366 2367 final PopupWindow p = new PopupWindow(this); 2368 p.setContentView(preview); 2369 p.setWidth((int) (sWidth * count + extraW)); 2370 p.setHeight((int) (sHeight + extraH)); 2371 p.setAnimationStyle(R.style.AnimationPreview); 2372 p.setOutsideTouchable(true); 2373 p.setFocusable(true); 2374 p.setBackgroundDrawable(new ColorDrawable(0)); 2375 p.showAsDropDown(anchor, 0, 0); 2376 2377 p.setOnDismissListener(new PopupWindow.OnDismissListener() { 2378 public void onDismiss() { 2379 dismissPreview(anchor); 2380 } 2381 }); 2382 2383 anchor.setTag(p); 2384 anchor.setTag(R.id.workspace, preview); 2385 anchor.setTag(R.id.icon, bitmaps); 2386 } 2387 2388 class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener { 2389 private final View mAnchor; 2390 2391 public PreviewTouchHandler(View anchor) { 2392 mAnchor = anchor; 2393 } 2394 2395 public void onClick(View v) { 2396 mWorkspace.snapToPage((Integer) v.getTag()); 2397 v.post(this); 2398 } 2399 2400 public void run() { 2401 dismissPreview(mAnchor); 2402 } 2403 2404 public void onFocusChange(View v, boolean hasFocus) { 2405 if (hasFocus) { 2406 mWorkspace.snapToPage((Integer) v.getTag()); 2407 } 2408 } 2409 } 2410 2411 Workspace getWorkspace() { 2412 return mWorkspace; 2413 } 2414 2415 TabHost getCustomizationDrawer() { 2416 return mHomeCustomizationDrawer; 2417 } 2418 2419 @Override 2420 protected Dialog onCreateDialog(int id) { 2421 switch (id) { 2422 case DIALOG_CREATE_SHORTCUT: 2423 return new CreateShortcut().createDialog(); 2424 case DIALOG_RENAME_FOLDER: 2425 return new RenameFolder().createDialog(); 2426 } 2427 2428 return super.onCreateDialog(id); 2429 } 2430 2431 @Override 2432 protected void onPrepareDialog(int id, Dialog dialog) { 2433 switch (id) { 2434 case DIALOG_CREATE_SHORTCUT: 2435 break; 2436 case DIALOG_RENAME_FOLDER: 2437 if (mFolderInfo != null) { 2438 EditText input = (EditText) dialog.findViewById(R.id.folder_name); 2439 final CharSequence text = mFolderInfo.title; 2440 input.setText(text); 2441 input.setSelection(0, text.length()); 2442 } 2443 break; 2444 } 2445 } 2446 2447 void showRenameDialog(FolderInfo info) { 2448 mFolderInfo = info; 2449 mWaitingForResult = true; 2450 showDialog(DIALOG_RENAME_FOLDER); 2451 } 2452 2453 private void showAddDialog(int intersectX, int intersectY) { 2454 resetAddInfo(); 2455 mAddIntersectCellX = intersectX; 2456 mAddIntersectCellY = intersectY; 2457 mAddScreen = mWorkspace.getCurrentPage(); 2458 mWaitingForResult = true; 2459 showDialog(DIALOG_CREATE_SHORTCUT); 2460 } 2461 2462 private void pickShortcut() { 2463 // Insert extra item to handle picking application 2464 Bundle bundle = new Bundle(); 2465 2466 ArrayList<String> shortcutNames = new ArrayList<String>(); 2467 shortcutNames.add(getString(R.string.group_applications)); 2468 bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); 2469 2470 ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>(); 2471 shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this, 2472 R.drawable.ic_launcher_application)); 2473 bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); 2474 2475 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); 2476 pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); 2477 pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_shortcut)); 2478 pickIntent.putExtras(bundle); 2479 2480 startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT); 2481 } 2482 2483 private class RenameFolder { 2484 private EditText mInput; 2485 2486 Dialog createDialog() { 2487 final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null); 2488 mInput = (EditText) layout.findViewById(R.id.folder_name); 2489 2490 AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this); 2491 builder.setIcon(0); 2492 builder.setTitle(getString(R.string.rename_folder_title)); 2493 builder.setCancelable(true); 2494 builder.setOnCancelListener(new Dialog.OnCancelListener() { 2495 public void onCancel(DialogInterface dialog) { 2496 cleanup(); 2497 } 2498 }); 2499 builder.setNegativeButton(getString(R.string.cancel_action), 2500 new Dialog.OnClickListener() { 2501 public void onClick(DialogInterface dialog, int which) { 2502 cleanup(); 2503 } 2504 } 2505 ); 2506 builder.setPositiveButton(getString(R.string.rename_action), 2507 new Dialog.OnClickListener() { 2508 public void onClick(DialogInterface dialog, int which) { 2509 changeFolderName(); 2510 } 2511 } 2512 ); 2513 builder.setView(layout); 2514 2515 final AlertDialog dialog = builder.create(); 2516 dialog.setOnShowListener(new DialogInterface.OnShowListener() { 2517 public void onShow(DialogInterface dialog) { 2518 mWaitingForResult = true; 2519 mInput.requestFocus(); 2520 InputMethodManager inputManager = (InputMethodManager) 2521 getSystemService(Context.INPUT_METHOD_SERVICE); 2522 inputManager.showSoftInput(mInput, 0); 2523 } 2524 }); 2525 2526 return dialog; 2527 } 2528 2529 private void changeFolderName() { 2530 final String name = mInput.getText().toString(); 2531 if (!TextUtils.isEmpty(name)) { 2532 // Make sure we have the right folder info 2533 mFolderInfo = sFolders.get(mFolderInfo.id); 2534 mFolderInfo.title = name; 2535 LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo); 2536 2537 if (mWorkspaceLoading) { 2538 lockAllApps(); 2539 mModel.startLoader(Launcher.this, false); 2540 } else { 2541 final FolderIcon folderIcon = (FolderIcon) 2542 mWorkspace.getViewForTag(mFolderInfo); 2543 if (folderIcon != null) { 2544 folderIcon.setText(name); 2545 getWorkspace().requestLayout(); 2546 } else { 2547 lockAllApps(); 2548 mWorkspaceLoading = true; 2549 mModel.startLoader(Launcher.this, false); 2550 } 2551 } 2552 } 2553 cleanup(); 2554 } 2555 2556 private void cleanup() { 2557 dismissDialog(DIALOG_RENAME_FOLDER); 2558 mWaitingForResult = false; 2559 mFolderInfo = null; 2560 } 2561 } 2562 2563 // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. 2564 public boolean isAllAppsVisible() { 2565 return mState == State.ALL_APPS; 2566 } 2567 2568 // AllAppsView.Watcher 2569 public void zoomed(float zoom) { 2570 // In XLarge view, we zoom down the workspace below all apps so it's still visible 2571 if (zoom == 1.0f && !LauncherApplication.isScreenXLarge()) { 2572 mWorkspace.setVisibility(View.GONE); 2573 } 2574 } 2575 2576 private void showAndEnableToolbarButton(View button) { 2577 button.setVisibility(View.VISIBLE); 2578 button.setFocusable(true); 2579 button.setClickable(true); 2580 } 2581 2582 private void hideToolbarButton(View button) { 2583 button.setAlpha(0.0f); 2584 // We can't set it to GONE, otherwise the RelativeLayout gets screwed up 2585 button.setVisibility(View.INVISIBLE); 2586 } 2587 2588 private void disableToolbarButton(View button) { 2589 button.setFocusable(false); 2590 button.setClickable(false); 2591 } 2592 2593 /** 2594 * Helper function for showing or hiding a toolbar button, possibly animated. 2595 * 2596 * @param show If true, create an animation to the show the item. Otherwise, hide it. 2597 * @param view The toolbar button to be animated 2598 * @param seq A AnimatorSet that will be used to animate the transition. If null, the 2599 * transition will not be animated. 2600 */ 2601 private void hideOrShowToolbarButton(boolean show, final View view, AnimatorSet seq) { 2602 final boolean showing = show; 2603 final boolean hiding = !show; 2604 2605 final int duration = show ? 2606 getResources().getInteger(R.integer.config_toolbarButtonFadeInTime) : 2607 getResources().getInteger(R.integer.config_toolbarButtonFadeOutTime); 2608 2609 if (seq != null) { 2610 ValueAnimator anim = ValueAnimator.ofFloat(view.getAlpha(), show ? 1.0f : 0.0f); 2611 anim.setDuration(duration); 2612 anim.addUpdateListener(new AnimatorUpdateListener() { 2613 public void onAnimationUpdate(ValueAnimator animation) { 2614 view.setAlpha((Float) animation.getAnimatedValue()); 2615 } 2616 }); 2617 anim.addListener(new AnimatorListenerAdapter() { 2618 @Override 2619 public void onAnimationStart(Animator animation) { 2620 if (showing) showAndEnableToolbarButton(view); 2621 if (hiding) disableToolbarButton(view); 2622 } 2623 @Override 2624 public void onAnimationEnd(Animator animation) { 2625 if (hiding) hideToolbarButton(view); 2626 } 2627 }); 2628 seq.play(anim); 2629 } else { 2630 if (showing) { 2631 showAndEnableToolbarButton(view); 2632 view.setAlpha(1f); 2633 } else { 2634 disableToolbarButton(view); 2635 hideToolbarButton(view); 2636 } 2637 } 2638 } 2639 2640 /** 2641 * Show/hide the appropriate toolbar buttons for newState. 2642 * If showSeq or hideSeq is null, the transition will be done immediately (not animated). 2643 * 2644 * @param newState The state that is being switched to 2645 * @param showSeq AnimatorSet in which to put "show" animations, or null. 2646 * @param hideSeq AnimatorSet in which to put "hide" animations, or null. 2647 */ 2648 private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) { 2649 switch (newState) { 2650 case WORKSPACE: 2651 hideOrShowToolbarButton(true, mButtonCluster, showSeq); 2652 mDeleteZone.setOverlappingViews( 2653 new View[] { mAllAppsButton, mDivider, mConfigureButton }); 2654 mDeleteZone.setDragAndDropEnabled(true); 2655 mDeleteZone.setText(getResources().getString(R.string.delete_zone_label_workspace)); 2656 break; 2657 case ALL_APPS: 2658 hideOrShowToolbarButton(false, mButtonCluster, hideSeq); 2659 mDeleteZone.setDragAndDropEnabled(false); 2660 mDeleteZone.setText(getResources().getString(R.string.delete_zone_label_all_apps)); 2661 break; 2662 case CUSTOMIZE: 2663 hideOrShowToolbarButton(false, mButtonCluster, hideSeq); 2664 mDeleteZone.setDragAndDropEnabled(false); 2665 break; 2666 } 2667 } 2668 2669 /** 2670 * Helper method for the cameraZoomIn/cameraZoomOut animations 2671 * @param view The view being animated 2672 * @param state The state that we are moving in or out of -- either ALL_APPS or CUSTOMIZE 2673 * @param scaleFactor The scale factor used for the zoom 2674 */ 2675 private void setPivotsForZoom(View view, State state, float scaleFactor) { 2676 final int height = view.getHeight(); 2677 2678 view.setPivotX(view.getWidth() / 2.0f); 2679 // Set pivotY so that at the starting zoom factor, the view is partially 2680 // visible. Modifying initialHeightFactor changes how much of the view is 2681 // initially showing, and hence the perceived angle from which the view enters. 2682 if (state == State.ALL_APPS) { 2683 final float initialHeightFactor = 0.175f; 2684 view.setPivotY((1 - initialHeightFactor) * height); 2685 } else { 2686 final float initialHeightFactor = 0.2f; 2687 view.setPivotY(-initialHeightFactor * height); 2688 } 2689 } 2690 2691 /** 2692 * Zoom the camera out from the workspace to reveal 'toView'. 2693 * Assumes that the view to show is anchored at either the very top or very bottom 2694 * of the screen. 2695 * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE. 2696 */ 2697 private void cameraZoomOut(State toState, boolean animated) { 2698 final Resources res = getResources(); 2699 final boolean toAllApps = (toState == State.ALL_APPS); 2700 2701 final int duration = toAllApps ? 2702 res.getInteger(R.integer.config_allAppsZoomInTime) : 2703 res.getInteger(R.integer.config_customizeZoomInTime); 2704 final int fadeDuration = toAllApps ? 2705 res.getInteger(R.integer.config_allAppsFadeInTime) : 2706 res.getInteger(R.integer.config_customizeFadeInTime); 2707 2708 final float scale = toAllApps ? 2709 (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor) : 2710 (float) res.getInteger(R.integer.config_customizeZoomScaleFactor); 2711 2712 final View toView = toAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; 2713 2714 setPivotsForZoom(toView, toState, scale); 2715 2716 if (toAllApps) { 2717 mWorkspace.shrink(ShrinkState.BOTTOM_HIDDEN, animated); 2718 } else { 2719 mWorkspace.shrink(ShrinkState.TOP, animated); 2720 } 2721 2722 if (animated) { 2723 final ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 2724 scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator()); 2725 scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 2726 public void onAnimationUpdate(float a, float b) { 2727 ((View) toView.getParent()).fastInvalidate(); 2728 toView.setFastScaleX(a * scale + b * 1f); 2729 toView.setFastScaleY(a * scale + b * 1f); 2730 } 2731 }); 2732 2733 if (toAllApps) { 2734 toView.setFastAlpha(0f); 2735 ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(fadeDuration); 2736 alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f)); 2737 alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 2738 public void onAnimationUpdate(float a, float b) { 2739 // don't need to invalidate because we do so above 2740 toView.setFastAlpha(a * 0f + b * 1f); 2741 } 2742 }); 2743 alphaAnim.start(); 2744 } 2745 2746 if (toView instanceof LauncherTransitionable) { 2747 ((LauncherTransitionable) toView).onLauncherTransitionStart(scaleAnim); 2748 } 2749 scaleAnim.addListener(new AnimatorListenerAdapter() { 2750 @Override 2751 public void onAnimationStart(Animator animation) { 2752 // Prepare the position 2753 toView.setTranslationX(0.0f); 2754 toView.setTranslationY(0.0f); 2755 toView.setVisibility(View.VISIBLE); 2756 if (!toAllApps) { 2757 toView.setFastAlpha(1.0f); 2758 } 2759 } 2760 @Override 2761 public void onAnimationEnd(Animator animation) { 2762 // If we don't set the final scale values here, if this animation is cancelled 2763 // it will have the wrong scale value and subsequent cameraPan animations will 2764 // not fix that 2765 toView.setScaleX(1.0f); 2766 toView.setScaleY(1.0f); 2767 if (toView instanceof LauncherTransitionable) { 2768 ((LauncherTransitionable) toView).onLauncherTransitionEnd(scaleAnim); 2769 } 2770 } 2771 }); 2772 2773 AnimatorSet toolbarHideAnim = new AnimatorSet(); 2774 AnimatorSet toolbarShowAnim = new AnimatorSet(); 2775 hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim); 2776 2777 // toView should appear right at the end of the workspace shrink animation 2778 final int startDelay = 0; 2779 2780 if (mStateAnimation != null) mStateAnimation.cancel(); 2781 mStateAnimation = new AnimatorSet(); 2782 mStateAnimation.playTogether(scaleAnim, toolbarHideAnim); 2783 mStateAnimation.play(scaleAnim).after(startDelay); 2784 2785 // Show the new toolbar buttons just as the main animation is ending 2786 final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); 2787 mStateAnimation.play(toolbarShowAnim).after(duration + startDelay - fadeInTime); 2788 mStateAnimation.start(); 2789 } else { 2790 toView.setTranslationX(0.0f); 2791 toView.setTranslationY(0.0f); 2792 toView.setScaleX(1.0f); 2793 toView.setScaleY(1.0f); 2794 toView.setVisibility(View.VISIBLE); 2795 if (toView instanceof LauncherTransitionable) { 2796 ((LauncherTransitionable) toView).onLauncherTransitionStart(null); 2797 ((LauncherTransitionable) toView).onLauncherTransitionEnd(null); 2798 } 2799 hideAndShowToolbarButtons(toState, null, null); 2800 } 2801 } 2802 2803 /** 2804 * Zoom the camera back into the workspace, hiding 'fromView'. 2805 * This is the opposite of cameraZoomOut. 2806 * @param fromState The current state (must be ALL_APPS or CUSTOMIZE). 2807 * @param animated If true, the transition will be animated. 2808 */ 2809 private void cameraZoomIn(State fromState, boolean animated) { 2810 cameraZoomIn(fromState, animated, false); 2811 } 2812 2813 private void cameraZoomIn(State fromState, boolean animated, boolean springLoaded) { 2814 Resources res = getResources(); 2815 final boolean fromAllApps = (fromState == State.ALL_APPS); 2816 2817 int duration = fromAllApps ? 2818 res.getInteger(R.integer.config_allAppsZoomOutTime) : 2819 res.getInteger(R.integer.config_customizeZoomOutTime); 2820 2821 final float scaleFactor = fromAllApps ? 2822 (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor) : 2823 (float) res.getInteger(R.integer.config_customizeZoomScaleFactor); 2824 2825 final View fromView = fromAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; 2826 2827 mCustomizePagedView.endChoiceMode(); 2828 mAllAppsPagedView.endChoiceMode(); 2829 2830 setPivotsForZoom(fromView, fromState, scaleFactor); 2831 2832 if (!springLoaded) { 2833 mWorkspace.unshrink(animated); 2834 } 2835 if (animated) { 2836 if (mStateAnimation != null) mStateAnimation.cancel(); 2837 mStateAnimation = new AnimatorSet(); 2838 2839 final float oldScaleX = fromView.getScaleX(); 2840 final float oldScaleY = fromView.getScaleY(); 2841 2842 ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 2843 scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator()); 2844 scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 2845 public void onAnimationUpdate(float a, float b) { 2846 ((View)fromView.getParent()).fastInvalidate(); 2847 fromView.setFastScaleX(a * oldScaleX + b * scaleFactor); 2848 fromView.setFastScaleY(a * oldScaleY + b * scaleFactor); 2849 } 2850 }); 2851 final ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f); 2852 alphaAnim.setDuration(res.getInteger(R.integer.config_allAppsFadeOutTime)); 2853 alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f)); 2854 alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 2855 public void onAnimationUpdate(float a, float b) { 2856 // don't need to invalidate because we do so above 2857 fromView.setFastAlpha(a * 1f + b * 0f); 2858 } 2859 }); 2860 if (fromView instanceof LauncherTransitionable) { 2861 ((LauncherTransitionable) fromView).onLauncherTransitionStart(alphaAnim); 2862 } 2863 alphaAnim.addListener(new AnimatorListenerAdapter() { 2864 @Override 2865 public void onAnimationEnd(Animator animation) { 2866 fromView.setVisibility(View.GONE); 2867 if (fromView instanceof LauncherTransitionable) { 2868 ((LauncherTransitionable) fromView).onLauncherTransitionEnd(alphaAnim); 2869 } 2870 } 2871 }); 2872 2873 AnimatorSet toolbarHideAnim = new AnimatorSet(); 2874 AnimatorSet toolbarShowAnim = new AnimatorSet(); 2875 if (!springLoaded) { 2876 hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim); 2877 } 2878 2879 mStateAnimation.playTogether(scaleAnim, toolbarHideAnim, alphaAnim); 2880 2881 // Show the new toolbar buttons at the very end of the whole animation 2882 final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); 2883 final int unshrinkTime = res.getInteger(R.integer.config_workspaceUnshrinkTime); 2884 mStateAnimation.play(toolbarShowAnim).after(unshrinkTime - fadeInTime); 2885 mStateAnimation.start(); 2886 } else { 2887 fromView.setVisibility(View.GONE); 2888 if (fromView instanceof LauncherTransitionable) { 2889 ((LauncherTransitionable) fromView).onLauncherTransitionStart(null); 2890 ((LauncherTransitionable) fromView).onLauncherTransitionEnd(null); 2891 } 2892 if (!springLoaded) { 2893 hideAndShowToolbarButtons(State.WORKSPACE, null, null); 2894 } 2895 } 2896 } 2897 2898 void showAllApps(boolean animated) { 2899 if (mState != State.WORKSPACE) { 2900 return; 2901 } 2902 2903 if (LauncherApplication.isScreenXLarge()) { 2904 cameraZoomOut(State.ALL_APPS, animated); 2905 } else { 2906 mAllAppsGrid.zoom(1.0f, animated); 2907 } 2908 2909 ((View) mAllAppsGrid).setFocusable(true); 2910 ((View) mAllAppsGrid).requestFocus(); 2911 2912 // TODO: fade these two too 2913 mDeleteZone.setVisibility(View.GONE); 2914 2915 // Change the state *after* we've called all the transition code 2916 mState = State.ALL_APPS; 2917 2918 // Pause the auto-advance of widgets until we are out of AllApps 2919 mUserPresent = false; 2920 updateRunning(); 2921 2922 // send an accessibility event to announce the context change 2923 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 2924 } 2925 2926 void showWorkspace(boolean animated) { 2927 showWorkspace(animated, null); 2928 } 2929 2930 void showWorkspace(boolean animated, CellLayout layout) { 2931 if (layout != null) { 2932 // always animated, but that's ok since we never specify a layout and 2933 // want no animation 2934 mWorkspace.unshrink(layout); 2935 } else { 2936 mWorkspace.unshrink(animated); 2937 } 2938 if (mState == State.ALL_APPS) { 2939 closeAllApps(animated); 2940 } else if (mState == State.CUSTOMIZE) { 2941 hideCustomizationDrawer(animated); 2942 } 2943 2944 // Change the state *after* we've called all the transition code 2945 mState = State.WORKSPACE; 2946 2947 // Resume the auto-advance of widgets 2948 mUserPresent = true; 2949 updateRunning(); 2950 2951 // send an accessibility event to announce the context change 2952 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 2953 } 2954 2955 void enterSpringLoadedDragMode(CellLayout layout) { 2956 mWorkspace.enterSpringLoadedDragMode(layout); 2957 if (mState == State.ALL_APPS) { 2958 cameraZoomIn(State.ALL_APPS, true, true); 2959 mState = State.ALL_APPS_SPRING_LOADED; 2960 } else if (mState == State.CUSTOMIZE) { 2961 cameraZoomIn(State.CUSTOMIZE, true, true); 2962 mState = State.CUSTOMIZE_SPRING_LOADED; 2963 }/* else { 2964 // we're already in spring loaded mode; don't do anything 2965 }*/ 2966 } 2967 2968 void exitSpringLoadedDragMode() { 2969 if (mState == State.ALL_APPS_SPRING_LOADED) { 2970 mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.BOTTOM_VISIBLE); 2971 cameraZoomOut(State.ALL_APPS, true); 2972 mState = State.ALL_APPS; 2973 } else if (mState == State.CUSTOMIZE_SPRING_LOADED) { 2974 mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.TOP); 2975 cameraZoomOut(State.CUSTOMIZE, true); 2976 mState = State.CUSTOMIZE; 2977 }/* else { 2978 // we're not in spring loaded mode; don't do anything 2979 }*/ 2980 } 2981 2982 /** 2983 * Things to test when changing this code. 2984 * - Home from workspace 2985 * - from center screen 2986 * - from other screens 2987 * - Home from all apps 2988 * - from center screen 2989 * - from other screens 2990 * - Back from all apps 2991 * - from center screen 2992 * - from other screens 2993 * - Launch app from workspace and quit 2994 * - with back 2995 * - with home 2996 * - Launch app from all apps and quit 2997 * - with back 2998 * - with home 2999 * - Go to a screen that's not the default, then all 3000 * apps, and launch and app, and go back 3001 * - with back 3002 * -with home 3003 * - On workspace, long press power and go back 3004 * - with back 3005 * - with home 3006 * - On all apps, long press power and go back 3007 * - with back 3008 * - with home 3009 * - On workspace, power off 3010 * - On all apps, power off 3011 * - Launch an app and turn off the screen while in that app 3012 * - Go back with home key 3013 * - Go back with back key TODO: make this not go to workspace 3014 * - From all apps 3015 * - From workspace 3016 * - Enter and exit car mode (becuase it causes an extra configuration changed) 3017 * - From all apps 3018 * - From the center workspace 3019 * - From another workspace 3020 */ 3021 void closeAllApps(boolean animated) { 3022 if (mState == State.ALL_APPS || mState == State.ALL_APPS_SPRING_LOADED) { 3023 mWorkspace.setVisibility(View.VISIBLE); 3024 if (LauncherApplication.isScreenXLarge()) { 3025 cameraZoomIn(State.ALL_APPS, animated); 3026 } else { 3027 mAllAppsGrid.zoom(0.0f, animated); 3028 } 3029 ((View)mAllAppsGrid).setFocusable(false); 3030 ((CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage())).getChildrenLayout().requestFocus(); 3031 } 3032 } 3033 3034 void lockAllApps() { 3035 // TODO 3036 } 3037 3038 void unlockAllApps() { 3039 // TODO 3040 } 3041 3042 // Show the customization drawer (only exists in x-large configuration) 3043 private void showCustomizationDrawer(boolean animated) { 3044 if (mState != State.WORKSPACE) { 3045 return; 3046 } 3047 3048 cameraZoomOut(State.CUSTOMIZE, animated); 3049 3050 // Change the state *after* we've called all the transition code 3051 mState = State.CUSTOMIZE; 3052 3053 // Pause the auto-advance of widgets until we are out of Customization drawer 3054 mUserPresent = false; 3055 updateRunning(); 3056 } 3057 3058 // Hide the customization drawer (only exists in x-large configuration) 3059 void hideCustomizationDrawer(boolean animated) { 3060 if (mState == State.CUSTOMIZE || mState == State.CUSTOMIZE_SPRING_LOADED) { 3061 cameraZoomIn(State.CUSTOMIZE, animated); 3062 } 3063 } 3064 3065 /** 3066 * Add an item from all apps or customize onto the given workspace screen. 3067 * If layout is null, add to the current screen. 3068 */ 3069 void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) { 3070 if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) { 3071 showOutOfSpaceMessage(); 3072 } else { 3073 layout.animateDrop(); 3074 } 3075 } 3076 3077 void onWorkspaceClick(CellLayout layout) { 3078 showWorkspace(true, layout); 3079 } 3080 3081 private Drawable getExternalPackageToolbarIcon(ComponentName activityName) { 3082 try { 3083 PackageManager packageManager = getPackageManager(); 3084 // Look for the toolbar icon specified in the activity meta-data 3085 Bundle metaData = packageManager.getActivityInfo( 3086 activityName, PackageManager.GET_META_DATA).metaData; 3087 if (metaData != null) { 3088 int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME); 3089 if (iconResId != 0) { 3090 Resources res = packageManager.getResourcesForActivity(activityName); 3091 return res.getDrawable(iconResId); 3092 } 3093 } 3094 } catch (NameNotFoundException e) { 3095 // Do nothing 3096 } 3097 return null; 3098 } 3099 3100 // if successful in getting icon, return it; otherwise, set button to use default drawable 3101 private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity( 3102 int buttonId, ComponentName activityName, int fallbackDrawableId) { 3103 TextView button = (TextView) findViewById(buttonId); 3104 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName); 3105 3106 // If we were unable to find the icon via the meta-data, use a generic one 3107 if (toolbarIcon == null) { 3108 button.setCompoundDrawablesWithIntrinsicBounds(fallbackDrawableId, 0, 0, 0); 3109 return null; 3110 } else { 3111 button.setCompoundDrawablesWithIntrinsicBounds(toolbarIcon, null, null, null); 3112 return toolbarIcon.getConstantState(); 3113 } 3114 } 3115 3116 // if successful in getting icon, return it; otherwise, set button to use default drawable 3117 private Drawable.ConstantState updateButtonWithIconFromExternalActivity( 3118 int buttonId, ComponentName activityName, int fallbackDrawableId) { 3119 ImageView button = (ImageView) findViewById(buttonId); 3120 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName); 3121 3122 // If we were unable to find the icon via the meta-data, use a generic one 3123 if (toolbarIcon == null) { 3124 button.setImageResource(fallbackDrawableId); 3125 return null; 3126 } else { 3127 button.setImageDrawable(toolbarIcon); 3128 return toolbarIcon.getConstantState(); 3129 } 3130 } 3131 3132 private void updateTextButtonWithDrawable(int buttonId, Drawable.ConstantState d) { 3133 TextView button = (TextView) findViewById(buttonId); 3134 button.setCompoundDrawables(d.newDrawable(getResources()), null, null, null); 3135 } 3136 3137 private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) { 3138 ImageView button = (ImageView) findViewById(buttonId); 3139 button.setImageDrawable(d.newDrawable(getResources())); 3140 } 3141 3142 private void updateGlobalSearchIcon() { 3143 if (LauncherApplication.isScreenXLarge()) { 3144 final SearchManager searchManager = 3145 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 3146 ComponentName activityName = searchManager.getGlobalSearchActivity(); 3147 if (activityName != null) { 3148 sGlobalSearchIcon = updateButtonWithIconFromExternalActivity( 3149 R.id.search_button, activityName, R.drawable.ic_generic_search); 3150 } else { 3151 findViewById(R.id.search_button).setVisibility(View.GONE); 3152 } 3153 } 3154 } 3155 3156 private void updateGlobalSearchIcon(Drawable.ConstantState d) { 3157 updateButtonWithDrawable(R.id.search_button, d); 3158 } 3159 3160 private void updateVoiceSearchIcon() { 3161 if (LauncherApplication.isScreenXLarge()) { 3162 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 3163 ComponentName activityName = intent.resolveActivity(getPackageManager()); 3164 if (activityName != null) { 3165 sVoiceSearchIcon = updateButtonWithIconFromExternalActivity( 3166 R.id.voice_button, activityName, R.drawable.ic_voice_search); 3167 } else { 3168 findViewById(R.id.voice_button).setVisibility(View.GONE); 3169 } 3170 } 3171 } 3172 3173 private void updateVoiceSearchIcon(Drawable.ConstantState d) { 3174 updateButtonWithDrawable(R.id.voice_button, d); 3175 } 3176 3177 /** 3178 * Sets the app market icon (shown when all apps is visible on x-large screens) 3179 */ 3180 private void updateAppMarketIcon() { 3181 if (LauncherApplication.isScreenXLarge()) { 3182 Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET); 3183 // Find the app market activity by resolving an intent. 3184 // (If multiple app markets are installed, it will return the ResolverActivity.) 3185 ComponentName activityName = intent.resolveActivity(getPackageManager()); 3186 if (activityName != null) { 3187 mAppMarketIntent = intent; 3188 sAppMarketIcon = updateTextButtonWithIconFromExternalActivity( 3189 R.id.market_button, activityName, R.drawable.app_market_generic); 3190 } 3191 } 3192 } 3193 3194 private void updateAppMarketIcon(Drawable.ConstantState d) { 3195 updateTextButtonWithDrawable(R.id.market_button, d); 3196 } 3197 3198 /** 3199 * Displays the shortcut creation dialog and launches, if necessary, the 3200 * appropriate activity. 3201 */ 3202 private class CreateShortcut implements DialogInterface.OnClickListener, 3203 DialogInterface.OnCancelListener, DialogInterface.OnDismissListener, 3204 DialogInterface.OnShowListener { 3205 3206 private AddAdapter mAdapter; 3207 3208 Dialog createDialog() { 3209 mAdapter = new AddAdapter(Launcher.this); 3210 3211 final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this); 3212 builder.setTitle(getString(R.string.menu_item_add_item)); 3213 builder.setAdapter(mAdapter, this); 3214 3215 builder.setInverseBackgroundForced(true); 3216 3217 AlertDialog dialog = builder.create(); 3218 dialog.setOnCancelListener(this); 3219 dialog.setOnDismissListener(this); 3220 dialog.setOnShowListener(this); 3221 3222 return dialog; 3223 } 3224 3225 public void onCancel(DialogInterface dialog) { 3226 mWaitingForResult = false; 3227 cleanup(); 3228 } 3229 3230 public void onDismiss(DialogInterface dialog) { 3231 } 3232 3233 private void cleanup() { 3234 try { 3235 dismissDialog(DIALOG_CREATE_SHORTCUT); 3236 } catch (Exception e) { 3237 // An exception is thrown if the dialog is not visible, which is fine 3238 } 3239 } 3240 3241 /** 3242 * Handle the action clicked in the "Add to home" dialog. 3243 */ 3244 public void onClick(DialogInterface dialog, int which) { 3245 Resources res = getResources(); 3246 cleanup(); 3247 3248 switch (which) { 3249 case AddAdapter.ITEM_SHORTCUT: { 3250 pickShortcut(); 3251 break; 3252 } 3253 3254 case AddAdapter.ITEM_APPWIDGET: { 3255 int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId(); 3256 3257 Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); 3258 pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 3259 // start the pick activity 3260 startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET); 3261 break; 3262 } 3263 3264 case AddAdapter.ITEM_LIVE_FOLDER: { 3265 // Insert extra item to handle inserting folder 3266 Bundle bundle = new Bundle(); 3267 3268 ArrayList<String> shortcutNames = new ArrayList<String>(); 3269 shortcutNames.add(res.getString(R.string.group_folder)); 3270 bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); 3271 3272 ArrayList<ShortcutIconResource> shortcutIcons = 3273 new ArrayList<ShortcutIconResource>(); 3274 shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this, 3275 R.drawable.ic_launcher_folder)); 3276 bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); 3277 3278 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); 3279 pickIntent.putExtra(Intent.EXTRA_INTENT, 3280 new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER)); 3281 pickIntent.putExtra(Intent.EXTRA_TITLE, 3282 getText(R.string.title_select_live_folder)); 3283 pickIntent.putExtras(bundle); 3284 3285 startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER); 3286 break; 3287 } 3288 3289 case AddAdapter.ITEM_WALLPAPER: { 3290 startWallpaper(); 3291 break; 3292 } 3293 } 3294 } 3295 3296 public void onShow(DialogInterface dialog) { 3297 mWaitingForResult = true; 3298 } 3299 } 3300 3301 /** 3302 * Receives notifications when applications are added/removed. 3303 */ 3304 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver { 3305 @Override 3306 public void onReceive(Context context, Intent intent) { 3307 closeSystemDialogs(); 3308 String reason = intent.getStringExtra("reason"); 3309 if (!"homekey".equals(reason)) { 3310 boolean animate = true; 3311 if (mPaused || "lock".equals(reason)) { 3312 animate = false; 3313 } 3314 showWorkspace(animate); 3315 } 3316 } 3317 } 3318 3319 /** 3320 * Receives notifications whenever the appwidgets are reset. 3321 */ 3322 private class AppWidgetResetObserver extends ContentObserver { 3323 public AppWidgetResetObserver() { 3324 super(new Handler()); 3325 } 3326 3327 @Override 3328 public void onChange(boolean selfChange) { 3329 onAppWidgetReset(); 3330 } 3331 } 3332 3333 /** 3334 * If the activity is currently paused, signal that we need to re-run the loader 3335 * in onResume. 3336 * 3337 * This needs to be called from incoming places where resources might have been loaded 3338 * while we are paused. That is becaues the Configuration might be wrong 3339 * when we're not running, and if it comes back to what it was when we 3340 * were paused, we are not restarted. 3341 * 3342 * Implementation of the method from LauncherModel.Callbacks. 3343 * 3344 * @return true if we are currently paused. The caller might be able to 3345 * skip some work in that case since we will come back again. 3346 */ 3347 public boolean setLoadOnResume() { 3348 if (mPaused) { 3349 Log.i(TAG, "setLoadOnResume"); 3350 mOnResumeNeedsLoad = true; 3351 return true; 3352 } else { 3353 return false; 3354 } 3355 } 3356 3357 /** 3358 * Implementation of the method from LauncherModel.Callbacks. 3359 */ 3360 public int getCurrentWorkspaceScreen() { 3361 if (mWorkspace != null) { 3362 return mWorkspace.getCurrentPage(); 3363 } else { 3364 return SCREEN_COUNT / 2; 3365 } 3366 } 3367 3368 void setAllAppsPagedView(AllAppsPagedView view) { 3369 mAllAppsPagedView = view; 3370 } 3371 3372 /** 3373 * Refreshes the shortcuts shown on the workspace. 3374 * 3375 * Implementation of the method from LauncherModel.Callbacks. 3376 */ 3377 public void startBinding() { 3378 final Workspace workspace = mWorkspace; 3379 3380 mWorkspace.clearDropTargets(); 3381 int count = workspace.getChildCount(); 3382 for (int i = 0; i < count; i++) { 3383 // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate(). 3384 final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i); 3385 layoutParent.removeAllViewsInLayout(); 3386 } 3387 3388 if (DEBUG_USER_INTERFACE) { 3389 android.widget.Button finishButton = new android.widget.Button(this); 3390 finishButton.setText("Finish"); 3391 workspace.addInScreen(finishButton, 1, 0, 0, 1, 1); 3392 3393 finishButton.setOnClickListener(new android.widget.Button.OnClickListener() { 3394 public void onClick(View v) { 3395 finish(); 3396 } 3397 }); 3398 } 3399 3400 // This wasn't being called before which resulted in a leak of AppWidgetHostViews (through 3401 // mDesktopItems -> AppWidgetInfo -> hostView). 3402 unbindDesktopItems(); 3403 } 3404 3405 /** 3406 * Bind the items start-end from the list. 3407 * 3408 * Implementation of the method from LauncherModel.Callbacks. 3409 */ 3410 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) { 3411 3412 setLoadOnResume(); 3413 3414 final Workspace workspace = mWorkspace; 3415 3416 for (int i=start; i<end; i++) { 3417 final ItemInfo item = shortcuts.get(i); 3418 mDesktopItems.add(item); 3419 switch (item.itemType) { 3420 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3421 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3422 final View shortcut = createShortcut((ShortcutInfo)item); 3423 workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1, 3424 false); 3425 break; 3426 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 3427 final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, 3428 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), 3429 (UserFolderInfo) item, mIconCache); 3430 workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1, 3431 false); 3432 break; 3433 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 3434 final FolderIcon newLiveFolder = LiveFolderIcon.fromXml( 3435 R.layout.live_folder_icon, this, 3436 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), 3437 (LiveFolderInfo) item); 3438 workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, 3439 false); 3440 break; 3441 } 3442 } 3443 3444 workspace.requestLayout(); 3445 } 3446 3447 /** 3448 * Implementation of the method from LauncherModel.Callbacks. 3449 */ 3450 public void bindFolders(HashMap<Long, FolderInfo> folders) { 3451 setLoadOnResume(); 3452 sFolders.clear(); 3453 sFolders.putAll(folders); 3454 } 3455 3456 /** 3457 * Add the views for a widget to the workspace. 3458 * 3459 * Implementation of the method from LauncherModel.Callbacks. 3460 */ 3461 public void bindAppWidget(LauncherAppWidgetInfo item) { 3462 setLoadOnResume(); 3463 3464 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; 3465 if (DEBUG_WIDGETS) { 3466 Log.d(TAG, "bindAppWidget: " + item); 3467 } 3468 final Workspace workspace = mWorkspace; 3469 3470 final int appWidgetId = item.appWidgetId; 3471 final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); 3472 if (DEBUG_WIDGETS) { 3473 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); 3474 } 3475 3476 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 3477 3478 item.hostView.setAppWidget(appWidgetId, appWidgetInfo); 3479 item.hostView.setTag(item); 3480 3481 workspace.addInScreen(item.hostView, item.screen, item.cellX, 3482 item.cellY, item.spanX, item.spanY, false); 3483 3484 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo); 3485 3486 workspace.requestLayout(); 3487 3488 mDesktopItems.add(item); 3489 3490 if (DEBUG_WIDGETS) { 3491 Log.d(TAG, "bound widget id="+item.appWidgetId+" in " 3492 + (SystemClock.uptimeMillis()-start) + "ms"); 3493 } 3494 } 3495 3496 /** 3497 * Callback saying that there aren't any more items to bind. 3498 * 3499 * Implementation of the method from LauncherModel.Callbacks. 3500 */ 3501 public void finishBindingItems() { 3502 setLoadOnResume(); 3503 3504 if (mSavedState != null) { 3505 if (!mWorkspace.hasFocus()) { 3506 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); 3507 } 3508 3509 final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); 3510 if (userFolders != null) { 3511 for (long folderId : userFolders) { 3512 final FolderInfo info = sFolders.get(folderId); 3513 if (info != null) { 3514 openFolder(info); 3515 } 3516 } 3517 final Folder openFolder = mWorkspace.getOpenFolder(); 3518 if (openFolder != null) { 3519 openFolder.requestFocus(); 3520 } 3521 } 3522 3523 mSavedState = null; 3524 } 3525 3526 if (mSavedInstanceState != null) { 3527 super.onRestoreInstanceState(mSavedInstanceState); 3528 mSavedInstanceState = null; 3529 } 3530 3531 // Workaround a bug that occurs when rotating the device while the customization mode is 3532 // open, we trigger a new layout on all the CellLayout children. 3533 if (LauncherApplication.isScreenXLarge() && (mState == State.CUSTOMIZE)) { 3534 final int childCount = mWorkspace.getChildCount(); 3535 for (int i = 0; i < childCount; ++i) { 3536 mWorkspace.getChildAt(i).requestLayout(); 3537 } 3538 } 3539 3540 mWorkspaceLoading = false; 3541 3542 // If we received the result of any pending adds while the loader was running (e.g. the 3543 // widget configuration forced an orientation change), process them now. 3544 for (int i = 0; i < sPendingAddList.size(); i++) { 3545 completeAdd(sPendingAddList.get(i)); 3546 } 3547 sPendingAddList.clear(); 3548 } 3549 3550 /** 3551 * Updates the icons on the launcher that are affected by changes to the package list 3552 * on the device. 3553 */ 3554 private void updateIconsAffectedByPackageManagerChanges() { 3555 updateAppMarketIcon(); 3556 updateGlobalSearchIcon(); 3557 updateVoiceSearchIcon(); 3558 } 3559 3560 /** 3561 * Add the icons for all apps. 3562 * 3563 * Implementation of the method from LauncherModel.Callbacks. 3564 */ 3565 public void bindAllApplications(ArrayList<ApplicationInfo> apps) { 3566 mAllAppsGrid.setApps(apps); 3567 if (mCustomizePagedView != null) { 3568 mCustomizePagedView.setApps(apps); 3569 } 3570 updateIconsAffectedByPackageManagerChanges(); 3571 } 3572 3573 /** 3574 * A package was installed. 3575 * 3576 * Implementation of the method from LauncherModel.Callbacks. 3577 */ 3578 public void bindAppsAdded(ArrayList<ApplicationInfo> apps) { 3579 setLoadOnResume(); 3580 removeDialog(DIALOG_CREATE_SHORTCUT); 3581 mAllAppsGrid.addApps(apps); 3582 if (mCustomizePagedView != null) { 3583 mCustomizePagedView.addApps(apps); 3584 } 3585 updateIconsAffectedByPackageManagerChanges(); 3586 } 3587 3588 /** 3589 * A package was updated. 3590 * 3591 * Implementation of the method from LauncherModel.Callbacks. 3592 */ 3593 public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) { 3594 setLoadOnResume(); 3595 removeDialog(DIALOG_CREATE_SHORTCUT); 3596 if (mWorkspace != null) { 3597 mWorkspace.updateShortcuts(apps); 3598 } 3599 if (mAllAppsGrid != null) { 3600 mAllAppsGrid.updateApps(apps); 3601 } 3602 if (mCustomizePagedView != null) { 3603 mCustomizePagedView.updateApps(apps); 3604 } 3605 updateIconsAffectedByPackageManagerChanges(); 3606 } 3607 3608 /** 3609 * A package was uninstalled. 3610 * 3611 * Implementation of the method from LauncherModel.Callbacks. 3612 */ 3613 public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) { 3614 removeDialog(DIALOG_CREATE_SHORTCUT); 3615 if (permanent) { 3616 mWorkspace.removeItems(apps); 3617 } 3618 mAllAppsGrid.removeApps(apps); 3619 if (mCustomizePagedView != null) { 3620 mCustomizePagedView.removeApps(apps); 3621 } 3622 updateIconsAffectedByPackageManagerChanges(); 3623 } 3624 3625 /** 3626 * A number of packages were updated. 3627 */ 3628 public void bindPackagesUpdated() { 3629 // update the customization drawer contents 3630 if (mCustomizePagedView != null) { 3631 mCustomizePagedView.update(); 3632 } 3633 } 3634 3635 private int mapConfigurationOriActivityInfoOri(int configOri) { 3636 final Display d = getWindowManager().getDefaultDisplay(); 3637 int naturalOri = Configuration.ORIENTATION_LANDSCAPE; 3638 switch (d.getRotation()) { 3639 case Surface.ROTATION_0: 3640 case Surface.ROTATION_180: 3641 // We are currently in the same basic orientation as the natural orientation 3642 naturalOri = configOri; 3643 break; 3644 case Surface.ROTATION_90: 3645 case Surface.ROTATION_270: 3646 // We are currently in the other basic orientation to the natural orientation 3647 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ? 3648 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; 3649 break; 3650 } 3651 3652 int[] oriMap = { 3653 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, 3654 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, 3655 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, 3656 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE 3657 }; 3658 // Since the map starts at portrait, we need to offset if this device's natural orientation 3659 // is landscape. 3660 int indexOffset = 0; 3661 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) { 3662 indexOffset = 1; 3663 } 3664 return oriMap[(d.getRotation() + indexOffset) % 4]; 3665 } 3666 public void lockScreenOrientation() { 3667 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources() 3668 .getConfiguration().orientation)); 3669 } 3670 public void unlockScreenOrientation() { 3671 mHandler.postDelayed(new Runnable() { 3672 public void run() { 3673 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 3674 } 3675 }, mRestoreScreenOrientationDelay); 3676 } 3677 3678 /** 3679 * Prints out out state for debugging. 3680 */ 3681 public void dumpState() { 3682 Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this); 3683 Log.d(TAG, "mSavedState=" + mSavedState); 3684 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); 3685 Log.d(TAG, "mRestoring=" + mRestoring); 3686 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); 3687 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); 3688 Log.d(TAG, "mDesktopItems.size=" + mDesktopItems.size()); 3689 Log.d(TAG, "sFolders.size=" + sFolders.size()); 3690 mModel.dumpState(); 3691 mAllAppsGrid.dumpState(); 3692 Log.d(TAG, "END launcher2 dump state"); 3693 } 3694} 3695 3696interface LauncherTransitionable { 3697 void onLauncherTransitionStart(Animator animation); 3698 void onLauncherTransitionEnd(Animator animation); 3699} 3700