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