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