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