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