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 18 package com.android.launcher3; 19 20 import android.animation.Animator; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.AnimatorSet; 23 import android.animation.ObjectAnimator; 24 import android.animation.PropertyValuesHolder; 25 import android.animation.TimeInterpolator; 26 import android.animation.ValueAnimator; 27 import android.annotation.TargetApi; 28 import android.app.Activity; 29 import android.app.ActivityManager; 30 import android.app.ActivityOptions; 31 import android.app.AlertDialog; 32 import android.app.SearchManager; 33 import android.appwidget.AppWidgetHostView; 34 import android.appwidget.AppWidgetManager; 35 import android.appwidget.AppWidgetProviderInfo; 36 import android.content.ActivityNotFoundException; 37 import android.content.BroadcastReceiver; 38 import android.content.ComponentCallbacks2; 39 import android.content.ComponentName; 40 import android.content.ContentResolver; 41 import android.content.Context; 42 import android.content.DialogInterface; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.content.SharedPreferences; 46 import android.content.pm.ActivityInfo; 47 import android.content.pm.ApplicationInfo; 48 import android.content.pm.PackageManager; 49 import android.content.pm.PackageManager.NameNotFoundException; 50 import android.content.res.Configuration; 51 import android.content.res.Resources; 52 import android.database.ContentObserver; 53 import android.graphics.Bitmap; 54 import android.graphics.Canvas; 55 import android.graphics.Color; 56 import android.graphics.Point; 57 import android.graphics.PorterDuff; 58 import android.graphics.Rect; 59 import android.graphics.drawable.Drawable; 60 import android.net.Uri; 61 import android.os.AsyncTask; 62 import android.os.Build; 63 import android.os.Bundle; 64 import android.os.Environment; 65 import android.os.Handler; 66 import android.os.Message; 67 import android.os.StrictMode; 68 import android.os.SystemClock; 69 import android.speech.RecognizerIntent; 70 import android.text.Selection; 71 import android.text.SpannableStringBuilder; 72 import android.text.TextUtils; 73 import android.text.method.TextKeyListener; 74 import android.util.DisplayMetrics; 75 import android.util.Log; 76 import android.view.ContextThemeWrapper; 77 import android.view.Display; 78 import android.view.Gravity; 79 import android.view.HapticFeedbackConstants; 80 import android.view.KeyEvent; 81 import android.view.LayoutInflater; 82 import android.view.Menu; 83 import android.view.MotionEvent; 84 import android.view.Surface; 85 import android.view.View; 86 import android.view.View.OnClickListener; 87 import android.view.View.OnLongClickListener; 88 import android.view.ViewAnimationUtils; 89 import android.view.ViewGroup; 90 import android.view.ViewTreeObserver; 91 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 92 import android.view.Window; 93 import android.view.WindowManager; 94 import android.view.accessibility.AccessibilityEvent; 95 import android.view.animation.AccelerateInterpolator; 96 import android.view.animation.DecelerateInterpolator; 97 import android.view.animation.Interpolator; 98 import android.view.inputmethod.InputMethodManager; 99 import android.widget.Advanceable; 100 import android.widget.FrameLayout; 101 import android.widget.ImageView; 102 import android.widget.TextView; 103 import android.widget.Toast; 104 105 import com.android.launcher3.DropTarget.DragObject; 106 import com.android.launcher3.PagedView.PageSwitchListener; 107 import com.android.launcher3.compat.AppWidgetManagerCompat; 108 import com.android.launcher3.compat.LauncherActivityInfoCompat; 109 import com.android.launcher3.compat.LauncherAppsCompat; 110 import com.android.launcher3.compat.PackageInstallerCompat; 111 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 112 import com.android.launcher3.compat.UserHandleCompat; 113 import com.android.launcher3.compat.UserManagerCompat; 114 115 import java.io.DataInputStream; 116 import java.io.DataOutputStream; 117 import java.io.File; 118 import java.io.FileDescriptor; 119 import java.io.FileNotFoundException; 120 import java.io.FileOutputStream; 121 import java.io.IOException; 122 import java.io.PrintWriter; 123 import java.lang.reflect.Field; 124 import java.lang.reflect.InvocationTargetException; 125 import java.lang.reflect.Method; 126 import java.text.DateFormat; 127 import java.util.ArrayList; 128 import java.util.Collection; 129 import java.util.Date; 130 import java.util.HashMap; 131 import java.util.List; 132 import java.util.concurrent.atomic.AtomicInteger; 133 134 /** 135 * Default launcher application. 136 */ 137 public class Launcher extends Activity 138 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, 139 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener { 140 static final String TAG = "Launcher"; 141 static final boolean LOGD = false; 142 143 static final boolean PROFILE_STARTUP = false; 144 static final boolean DEBUG_WIDGETS = false; 145 static final boolean DEBUG_STRICT_MODE = false; 146 static final boolean DEBUG_RESUME_TIME = false; 147 static final boolean DEBUG_DUMP_LOG = false; 148 149 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run 150 151 private static final int REQUEST_CREATE_SHORTCUT = 1; 152 private static final int REQUEST_CREATE_APPWIDGET = 5; 153 private static final int REQUEST_PICK_SHORTCUT = 7; 154 private static final int REQUEST_PICK_APPWIDGET = 9; 155 private static final int REQUEST_PICK_WALLPAPER = 10; 156 157 private static final int REQUEST_BIND_APPWIDGET = 11; 158 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12; 159 160 /** 161 * IntentStarter uses request codes starting with this. This must be greater than all activity 162 * request codes used internally. 163 */ 164 protected static final int REQUEST_LAST = 100; 165 166 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; 167 168 static final int SCREEN_COUNT = 5; 169 static final int DEFAULT_SCREEN = 2; 170 171 private static final String PREFERENCES = "launcher.preferences"; 172 // To turn on these properties, type 173 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS] 174 static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate"; 175 static final String DUMP_STATE_PROPERTY = "launcher_dump_state"; 176 static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps"; 177 178 // The Intent extra that defines whether to ignore the launch animation 179 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = 180 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; 181 182 // Type: int 183 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; 184 // Type: int 185 private static final String RUNTIME_STATE = "launcher.state"; 186 // Type: int 187 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container"; 188 // Type: int 189 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; 190 // Type: int 191 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; 192 // Type: int 193 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; 194 // Type: boolean 195 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder"; 196 // Type: long 197 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; 198 // Type: int 199 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; 200 // Type: int 201 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y"; 202 // Type: parcelable 203 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; 204 // Type: parcelable 205 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id"; 206 // Type: int[] 207 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids"; 208 209 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed"; 210 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed"; 211 212 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete"; 213 static final String ACTION_FIRST_LOAD_COMPLETE = 214 "com.android.launcher3.action.FIRST_LOAD_COMPLETE"; 215 216 private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; 217 private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME = 218 "com.android.launcher.toolbar_search_icon"; 219 private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME = 220 "com.android.launcher.toolbar_voice_search_icon"; 221 222 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem"; 223 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false; 224 225 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data"; 226 227 /** The different states that Launcher can be in. */ 228 private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED }; 229 private State mState = State.WORKSPACE; 230 private AnimatorSet mStateAnimation; 231 232 private boolean mIsSafeModeEnabled; 233 234 static final int APPWIDGET_HOST_ID = 1024; 235 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300; 236 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; 237 private static final int ACTIVITY_START_DELAY = 1000; 238 239 private static final Object sLock = new Object(); 240 private static int sScreen = DEFAULT_SCREEN; 241 242 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>(); 243 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); 244 245 // How long to wait before the new-shortcut animation automatically pans the workspace 246 private static int NEW_APPS_PAGE_MOVE_DELAY = 500; 247 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; 248 private static int NEW_APPS_ANIMATION_DELAY = 500; 249 private static final int SINGLE_FRAME_DELAY = 16; 250 251 private final BroadcastReceiver mCloseSystemDialogsReceiver 252 = new CloseSystemDialogsIntentReceiver(); 253 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver(); 254 255 private LayoutInflater mInflater; 256 257 private Workspace mWorkspace; 258 private View mLauncherView; 259 private View mPageIndicators; 260 private DragLayer mDragLayer; 261 private DragController mDragController; 262 private View mWeightWatcher; 263 264 private AppWidgetManagerCompat mAppWidgetManager; 265 private LauncherAppWidgetHost mAppWidgetHost; 266 267 private ItemInfo mPendingAddInfo = new ItemInfo(); 268 private AppWidgetProviderInfo mPendingAddWidgetInfo; 269 private int mPendingAddWidgetId = -1; 270 271 private int[] mTmpAddItemCellCoordinates = new int[2]; 272 273 private FolderInfo mFolderInfo; 274 275 private Hotseat mHotseat; 276 private ViewGroup mOverviewPanel; 277 278 private View mAllAppsButton; 279 280 private SearchDropTargetBar mSearchDropTargetBar; 281 private AppsCustomizeTabHost mAppsCustomizeTabHost; 282 private AppsCustomizePagedView mAppsCustomizeContent; 283 private boolean mAutoAdvanceRunning = false; 284 private View mQsb; 285 286 private Bundle mSavedState; 287 // We set the state in both onCreate and then onNewIntent in some cases, which causes both 288 // scroll issues (because the workspace may not have been measured yet) and extra work. 289 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume. 290 private State mOnResumeState = State.NONE; 291 292 private SpannableStringBuilder mDefaultKeySsb = null; 293 294 private boolean mWorkspaceLoading = true; 295 296 private boolean mPaused = true; 297 private boolean mRestoring; 298 private boolean mWaitingForResult; 299 private boolean mOnResumeNeedsLoad; 300 301 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>(); 302 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>(); 303 304 private Bundle mSavedInstanceState; 305 306 private LauncherModel mModel; 307 private IconCache mIconCache; 308 private boolean mUserPresent = true; 309 private boolean mVisible = false; 310 private boolean mHasFocus = false; 311 private boolean mAttached = false; 312 313 private static LocaleConfiguration sLocaleConfiguration = null; 314 315 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>(); 316 317 private View.OnTouchListener mHapticFeedbackTouchListener; 318 319 // Related to the auto-advancing of widgets 320 private final int ADVANCE_MSG = 1; 321 private final int mAdvanceInterval = 20000; 322 private final int mAdvanceStagger = 250; 323 private long mAutoAdvanceSentTime; 324 private long mAutoAdvanceTimeLeft = -1; 325 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = 326 new HashMap<View, AppWidgetProviderInfo>(); 327 328 // Determines how long to wait after a rotation before restoring the screen orientation to 329 // match the sensor state. 330 private final int mRestoreScreenOrientationDelay = 500; 331 332 // External icons saved in case of resource changes, orientation, etc. 333 private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2]; 334 private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; 335 336 private Drawable mWorkspaceBackgroundDrawable; 337 338 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); 339 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false; 340 341 static final ArrayList<String> sDumpLogs = new ArrayList<String>(); 342 static Date sDateStamp = new Date(); 343 static DateFormat sDateFormat = 344 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); 345 static long sRunStart = System.currentTimeMillis(); 346 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent"; 347 348 // We only want to get the SharedPreferences once since it does an FS stat each time we get 349 // it from the context. 350 private SharedPreferences mSharedPrefs; 351 352 private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null; 353 354 // Holds the page that we need to animate to, and the icon views that we need to animate up 355 // when we scroll to that page on resume. 356 private ImageView mFolderIconImageView; 357 private Bitmap mFolderIconBitmap; 358 private Canvas mFolderIconCanvas; 359 private Rect mRectForFolderAnimation = new Rect(); 360 361 private BubbleTextView mWaitingForResume; 362 363 private Runnable mBuildLayersRunnable = new Runnable() { 364 public void run() { 365 if (mWorkspace != null) { 366 mWorkspace.buildPageHardwareLayers(); 367 } 368 } 369 }; 370 371 private static PendingAddArguments sPendingAddItem; 372 373 public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY); 374 375 private static class PendingAddArguments { 376 int requestCode; 377 Intent intent; 378 long container; 379 long screenId; 380 int cellX; 381 int cellY; 382 int appWidgetId; 383 } 384 385 private Stats mStats; 386 387 FocusIndicatorView mFocusHandler; 388 389 static boolean isPropertyEnabled(String propertyName) { 390 return Log.isLoggable(propertyName, Log.VERBOSE); 391 } 392 393 @Override 394 protected void onCreate(Bundle savedInstanceState) { 395 if (DEBUG_STRICT_MODE) { 396 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 397 .detectDiskReads() 398 .detectDiskWrites() 399 .detectNetwork() // or .detectAll() for all detectable problems 400 .penaltyLog() 401 .build()); 402 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 403 .detectLeakedSqlLiteObjects() 404 .detectLeakedClosableObjects() 405 .penaltyLog() 406 .penaltyDeath() 407 .build()); 408 } 409 410 super.onCreate(savedInstanceState); 411 412 LauncherAppState.setApplicationContext(getApplicationContext()); 413 LauncherAppState app = LauncherAppState.getInstance(); 414 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this); 415 // Determine the dynamic grid properties 416 Point smallestSize = new Point(); 417 Point largestSize = new Point(); 418 Point realSize = new Point(); 419 Display display = getWindowManager().getDefaultDisplay(); 420 display.getCurrentSizeRange(smallestSize, largestSize); 421 display.getRealSize(realSize); 422 DisplayMetrics dm = new DisplayMetrics(); 423 display.getMetrics(dm); 424 425 // Lazy-initialize the dynamic grid 426 DeviceProfile grid = app.initDynamicGrid(this, 427 Math.min(smallestSize.x, smallestSize.y), 428 Math.min(largestSize.x, largestSize.y), 429 realSize.x, realSize.y, 430 dm.widthPixels, dm.heightPixels); 431 432 // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet 433 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), 434 Context.MODE_PRIVATE); 435 mIsSafeModeEnabled = getPackageManager().isSafeMode(); 436 mModel = app.setLauncher(this); 437 mIconCache = app.getIconCache(); 438 mIconCache.flushInvalidIcons(grid); 439 mDragController = new DragController(this); 440 mInflater = getLayoutInflater(); 441 442 mStats = new Stats(this); 443 444 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); 445 446 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); 447 mAppWidgetHost.startListening(); 448 449 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, 450 // this also ensures that any synchronous binding below doesn't re-trigger another 451 // LauncherModel load. 452 mPaused = false; 453 454 if (PROFILE_STARTUP) { 455 android.os.Debug.startMethodTracing( 456 Environment.getExternalStorageDirectory() + "/launcher"); 457 } 458 459 checkForLocaleChange(); 460 setContentView(R.layout.launcher); 461 462 setupViews(); 463 grid.layout(this); 464 465 registerContentObservers(); 466 467 lockAllApps(); 468 469 mSavedState = savedInstanceState; 470 restoreState(mSavedState); 471 472 if (PROFILE_STARTUP) { 473 android.os.Debug.stopMethodTracing(); 474 } 475 476 if (!mRestoring) { 477 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { 478 // If the user leaves launcher, then we should just load items asynchronously when 479 // they return. 480 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); 481 } else { 482 // We only load the page synchronously if the user rotates (or triggers a 483 // configuration change) while launcher is in the foreground 484 mModel.startLoader(true, mWorkspace.getRestorePage()); 485 } 486 } 487 488 // For handling default keys 489 mDefaultKeySsb = new SpannableStringBuilder(); 490 Selection.setSelection(mDefaultKeySsb, 0); 491 492 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 493 registerReceiver(mCloseSystemDialogsReceiver, filter); 494 495 updateGlobalIcons(); 496 497 // On large interfaces, we want the screen to auto-rotate based on the current orientation 498 unlockScreenOrientation(true); 499 500 if (shouldShowIntroScreen()) { 501 showIntroScreen(); 502 } else { 503 showFirstRunActivity(); 504 showFirstRunClings(); 505 } 506 } 507 508 @Override 509 public void onLauncherProviderChange() { } 510 511 /** To be overriden by subclasses to hint to Launcher that we have custom content */ 512 protected boolean hasCustomContentToLeft() { 513 return false; 514 } 515 516 /** 517 * To be overridden by subclasses to populate the custom content container and call 518 * {@link #addToCustomContentPage}. This will only be invoked if 519 * {@link #hasCustomContentToLeft()} is {@code true}. 520 */ 521 protected void populateCustomContentContainer() { 522 } 523 524 /** 525 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to 526 * ensure the custom content page is added or removed if necessary. 527 */ 528 protected void invalidateHasCustomContentToLeft() { 529 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) { 530 // Not bound yet, wait for bindScreens to be called. 531 return; 532 } 533 534 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) { 535 // Create the custom content page and call the subclass to populate it. 536 mWorkspace.createCustomContentContainer(); 537 populateCustomContentContainer(); 538 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) { 539 mWorkspace.removeCustomContentPage(); 540 } 541 } 542 543 private void updateGlobalIcons() { 544 boolean searchVisible = false; 545 boolean voiceVisible = false; 546 // If we have a saved version of these external icons, we load them up immediately 547 int coi = getCurrentOrientationIndexForGlobalIcons(); 548 if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null) { 549 searchVisible = updateGlobalSearchIcon(); 550 voiceVisible = updateVoiceSearchIcon(searchVisible); 551 } 552 if (sGlobalSearchIcon[coi] != null) { 553 updateGlobalSearchIcon(sGlobalSearchIcon[coi]); 554 searchVisible = true; 555 } 556 if (sVoiceSearchIcon[coi] != null) { 557 updateVoiceSearchIcon(sVoiceSearchIcon[coi]); 558 voiceVisible = true; 559 } 560 if (mSearchDropTargetBar != null) { 561 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); 562 } 563 } 564 565 private void checkForLocaleChange() { 566 if (sLocaleConfiguration == null) { 567 new AsyncTask<Void, Void, LocaleConfiguration>() { 568 @Override 569 protected LocaleConfiguration doInBackground(Void... unused) { 570 LocaleConfiguration localeConfiguration = new LocaleConfiguration(); 571 readConfiguration(Launcher.this, localeConfiguration); 572 return localeConfiguration; 573 } 574 575 @Override 576 protected void onPostExecute(LocaleConfiguration result) { 577 sLocaleConfiguration = result; 578 checkForLocaleChange(); // recursive, but now with a locale configuration 579 } 580 }.execute(); 581 return; 582 } 583 584 final Configuration configuration = getResources().getConfiguration(); 585 586 final String previousLocale = sLocaleConfiguration.locale; 587 final String locale = configuration.locale.toString(); 588 589 final int previousMcc = sLocaleConfiguration.mcc; 590 final int mcc = configuration.mcc; 591 592 final int previousMnc = sLocaleConfiguration.mnc; 593 final int mnc = configuration.mnc; 594 595 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; 596 597 if (localeChanged) { 598 sLocaleConfiguration.locale = locale; 599 sLocaleConfiguration.mcc = mcc; 600 sLocaleConfiguration.mnc = mnc; 601 602 mIconCache.flush(); 603 604 final LocaleConfiguration localeConfiguration = sLocaleConfiguration; 605 new AsyncTask<Void, Void, Void>() { 606 public Void doInBackground(Void ... args) { 607 writeConfiguration(Launcher.this, localeConfiguration); 608 return null; 609 } 610 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); 611 } 612 } 613 614 private static class LocaleConfiguration { 615 public String locale; 616 public int mcc = -1; 617 public int mnc = -1; 618 } 619 620 private static void readConfiguration(Context context, LocaleConfiguration configuration) { 621 DataInputStream in = null; 622 try { 623 in = new DataInputStream(context.openFileInput(PREFERENCES)); 624 configuration.locale = in.readUTF(); 625 configuration.mcc = in.readInt(); 626 configuration.mnc = in.readInt(); 627 } catch (FileNotFoundException e) { 628 // Ignore 629 } catch (IOException e) { 630 // Ignore 631 } finally { 632 if (in != null) { 633 try { 634 in.close(); 635 } catch (IOException e) { 636 // Ignore 637 } 638 } 639 } 640 } 641 642 private static void writeConfiguration(Context context, LocaleConfiguration configuration) { 643 DataOutputStream out = null; 644 try { 645 out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE)); 646 out.writeUTF(configuration.locale); 647 out.writeInt(configuration.mcc); 648 out.writeInt(configuration.mnc); 649 out.flush(); 650 } catch (FileNotFoundException e) { 651 // Ignore 652 } catch (IOException e) { 653 //noinspection ResultOfMethodCallIgnored 654 context.getFileStreamPath(PREFERENCES).delete(); 655 } finally { 656 if (out != null) { 657 try { 658 out.close(); 659 } catch (IOException e) { 660 // Ignore 661 } 662 } 663 } 664 } 665 666 public Stats getStats() { 667 return mStats; 668 } 669 670 public LayoutInflater getInflater() { 671 return mInflater; 672 } 673 674 boolean isDraggingEnabled() { 675 // We prevent dragging when we are loading the workspace as it is possible to pick up a view 676 // that is subsequently removed from the workspace in startBinding(). 677 return !mModel.isLoadingWorkspace(); 678 } 679 680 static int getScreen() { 681 synchronized (sLock) { 682 return sScreen; 683 } 684 } 685 686 static void setScreen(int screen) { 687 synchronized (sLock) { 688 sScreen = screen; 689 } 690 } 691 692 public static int generateViewId() { 693 if (Build.VERSION.SDK_INT >= 17) { 694 return View.generateViewId(); 695 } else { 696 // View.generateViewId() is not available. The following fallback logic is a copy 697 // of its implementation. 698 for (;;) { 699 final int result = sNextGeneratedId.get(); 700 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 701 int newValue = result + 1; 702 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. 703 if (sNextGeneratedId.compareAndSet(result, newValue)) { 704 return result; 705 } 706 } 707 } 708 } 709 710 public int getViewIdForItem(ItemInfo info) { 711 // This cast is safe given the > 2B range for int. 712 int itemId = (int) info.id; 713 if (mItemIdToViewId.containsKey(itemId)) { 714 return mItemIdToViewId.get(itemId); 715 } 716 int viewId = generateViewId(); 717 mItemIdToViewId.put(itemId, viewId); 718 return viewId; 719 } 720 721 /** 722 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have 723 * a configuration step, this allows the proper animations to run after other transitions. 724 */ 725 private long completeAdd(PendingAddArguments args) { 726 long screenId = args.screenId; 727 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 728 // When the screen id represents an actual screen (as opposed to a rank) we make sure 729 // that the drop page actually exists. 730 screenId = ensurePendingDropLayoutExists(args.screenId); 731 } 732 733 switch (args.requestCode) { 734 case REQUEST_CREATE_SHORTCUT: 735 completeAddShortcut(args.intent, args.container, screenId, args.cellX, 736 args.cellY); 737 break; 738 case REQUEST_CREATE_APPWIDGET: 739 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null); 740 break; 741 case REQUEST_RECONFIGURE_APPWIDGET: 742 completeRestoreAppWidget(args.appWidgetId); 743 break; 744 } 745 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, 746 // if you turned the screen off and then back while in All Apps, Launcher would not 747 // return to the workspace. Clearing mAddInfo.container here fixes this issue 748 resetAddInfo(); 749 return screenId; 750 } 751 752 @Override 753 protected void onActivityResult( 754 final int requestCode, final int resultCode, final Intent data) { 755 // Reset the startActivity waiting flag 756 setWaitingForResult(false); 757 final int pendingAddWidgetId = mPendingAddWidgetId; 758 mPendingAddWidgetId = -1; 759 760 Runnable exitSpringLoaded = new Runnable() { 761 @Override 762 public void run() { 763 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 764 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 765 } 766 }; 767 768 if (requestCode == REQUEST_BIND_APPWIDGET) { 769 final int appWidgetId = data != null ? 770 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; 771 if (resultCode == RESULT_CANCELED) { 772 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); 773 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 774 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 775 } else if (resultCode == RESULT_OK) { 776 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, 777 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY); 778 } 779 return; 780 } else if (requestCode == REQUEST_PICK_WALLPAPER) { 781 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) { 782 mWorkspace.exitOverviewMode(false); 783 } 784 return; 785 } 786 787 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || 788 requestCode == REQUEST_CREATE_APPWIDGET); 789 790 final boolean workspaceLocked = isWorkspaceLocked(); 791 // We have special handling for widgets 792 if (isWidgetDrop) { 793 final int appWidgetId; 794 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) 795 : -1; 796 if (widgetId < 0) { 797 appWidgetId = pendingAddWidgetId; 798 } else { 799 appWidgetId = widgetId; 800 } 801 802 final int result; 803 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { 804 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + 805 "returned from the widget configuration activity."); 806 result = RESULT_CANCELED; 807 completeTwoStageWidgetDrop(result, appWidgetId); 808 final Runnable onComplete = new Runnable() { 809 @Override 810 public void run() { 811 exitSpringLoadedDragModeDelayed(false, 0, null); 812 } 813 }; 814 if (workspaceLocked) { 815 // No need to remove the empty screen if we're mid-binding, as the 816 // the bind will not add the empty screen. 817 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY); 818 } else { 819 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 820 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 821 } 822 } else { 823 if (!workspaceLocked) { 824 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 825 // When the screen id represents an actual screen (as opposed to a rank) 826 // we make sure that the drop page actually exists. 827 mPendingAddInfo.screenId = 828 ensurePendingDropLayoutExists(mPendingAddInfo.screenId); 829 } 830 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId); 831 832 dropLayout.setDropPending(true); 833 final Runnable onComplete = new Runnable() { 834 @Override 835 public void run() { 836 completeTwoStageWidgetDrop(resultCode, appWidgetId); 837 dropLayout.setDropPending(false); 838 } 839 }; 840 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 841 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 842 } else { 843 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId, 844 mPendingAddInfo); 845 sPendingAddItem = args; 846 } 847 } 848 return; 849 } 850 851 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) { 852 if (resultCode == RESULT_OK) { 853 // Update the widget view. 854 PendingAddArguments args = preparePendingAddArgs(requestCode, data, 855 pendingAddWidgetId, mPendingAddInfo); 856 if (workspaceLocked) { 857 sPendingAddItem = args; 858 } else { 859 completeAdd(args); 860 } 861 } 862 // Leave the widget in the pending state if the user canceled the configure. 863 return; 864 } 865 866 // The pattern used here is that a user PICKs a specific application, 867 // which, depending on the target, might need to CREATE the actual target. 868 869 // For example, the user would PICK_SHORTCUT for "Music playlist", and we 870 // launch over to the Music app to actually CREATE_SHORTCUT. 871 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { 872 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, 873 mPendingAddInfo); 874 if (isWorkspaceLocked()) { 875 sPendingAddItem = args; 876 } else { 877 completeAdd(args); 878 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 879 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 880 } 881 } else if (resultCode == RESULT_CANCELED) { 882 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 883 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 884 } 885 mDragLayer.clearAnimatedView(); 886 } 887 888 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int 889 appWidgetId, ItemInfo info) { 890 PendingAddArguments args = new PendingAddArguments(); 891 args.requestCode = requestCode; 892 args.intent = data; 893 args.container = info.container; 894 args.screenId = info.screenId; 895 args.cellX = info.cellX; 896 args.cellY = info.cellY; 897 args.appWidgetId = appWidgetId; 898 return args; 899 } 900 901 /** 902 * Check to see if a given screen id exists. If not, create it at the end, return the new id. 903 * 904 * @param screenId the screen id to check 905 * @return the new screen, or screenId if it exists 906 */ 907 private long ensurePendingDropLayoutExists(long screenId) { 908 CellLayout dropLayout = 909 (CellLayout) mWorkspace.getScreenWithId(screenId); 910 if (dropLayout == null) { 911 // it's possible that the add screen was removed because it was 912 // empty and a re-bind occurred 913 mWorkspace.addExtraEmptyScreen(); 914 return mWorkspace.commitExtraEmptyScreen(); 915 } else { 916 return screenId; 917 } 918 } 919 920 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) { 921 CellLayout cellLayout = 922 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId); 923 Runnable onCompleteRunnable = null; 924 int animationType = 0; 925 926 AppWidgetHostView boundWidget = null; 927 if (resultCode == RESULT_OK) { 928 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; 929 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, 930 mPendingAddWidgetInfo); 931 boundWidget = layout; 932 onCompleteRunnable = new Runnable() { 933 @Override 934 public void run() { 935 completeAddAppWidget(appWidgetId, mPendingAddInfo.container, 936 mPendingAddInfo.screenId, layout, null); 937 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 938 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 939 } 940 }; 941 } else if (resultCode == RESULT_CANCELED) { 942 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 943 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; 944 } 945 if (mDragLayer.getAnimatedView() != null) { 946 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, 947 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, 948 animationType, boundWidget, true); 949 } else if (onCompleteRunnable != null) { 950 // The animated view may be null in the case of a rotation during widget configuration 951 onCompleteRunnable.run(); 952 } 953 } 954 955 @Override 956 protected void onStop() { 957 super.onStop(); 958 FirstFrameAnimatorHelper.setIsVisible(false); 959 } 960 961 @Override 962 protected void onStart() { 963 super.onStart(); 964 FirstFrameAnimatorHelper.setIsVisible(true); 965 } 966 967 @Override 968 protected void onResume() { 969 long startTime = 0; 970 if (DEBUG_RESUME_TIME) { 971 startTime = System.currentTimeMillis(); 972 Log.v(TAG, "Launcher.onResume()"); 973 } 974 super.onResume(); 975 976 // Restore the previous launcher state 977 if (mOnResumeState == State.WORKSPACE) { 978 showWorkspace(false); 979 } else if (mOnResumeState == State.APPS_CUSTOMIZE) { 980 showAllApps(false, mAppsCustomizeContent.getContentType(), false); 981 } 982 mOnResumeState = State.NONE; 983 984 // Background was set to gradient in onPause(), restore to black if in all apps. 985 setWorkspaceBackground(mState == State.WORKSPACE); 986 987 mPaused = false; 988 if (mRestoring || mOnResumeNeedsLoad) { 989 setWorkspaceLoading(true); 990 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); 991 mRestoring = false; 992 mOnResumeNeedsLoad = false; 993 } 994 if (mBindOnResumeCallbacks.size() > 0) { 995 // We might have postponed some bind calls until onResume (see waitUntilResume) -- 996 // execute them here 997 long startTimeCallbacks = 0; 998 if (DEBUG_RESUME_TIME) { 999 startTimeCallbacks = System.currentTimeMillis(); 1000 } 1001 1002 if (mAppsCustomizeContent != null) { 1003 mAppsCustomizeContent.setBulkBind(true); 1004 } 1005 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) { 1006 mBindOnResumeCallbacks.get(i).run(); 1007 } 1008 if (mAppsCustomizeContent != null) { 1009 mAppsCustomizeContent.setBulkBind(false); 1010 } 1011 mBindOnResumeCallbacks.clear(); 1012 if (DEBUG_RESUME_TIME) { 1013 Log.d(TAG, "Time spent processing callbacks in onResume: " + 1014 (System.currentTimeMillis() - startTimeCallbacks)); 1015 } 1016 } 1017 if (mOnResumeCallbacks.size() > 0) { 1018 for (int i = 0; i < mOnResumeCallbacks.size(); i++) { 1019 mOnResumeCallbacks.get(i).run(); 1020 } 1021 mOnResumeCallbacks.clear(); 1022 } 1023 1024 // Reset the pressed state of icons that were locked in the press state while activities 1025 // were launching 1026 if (mWaitingForResume != null) { 1027 // Resets the previous workspace icon press state 1028 mWaitingForResume.setStayPressed(false); 1029 } 1030 1031 // It is possible that widgets can receive updates while launcher is not in the foreground. 1032 // Consequently, the widgets will be inflated in the orientation of the foreground activity 1033 // (framework issue). On resuming, we ensure that any widgets are inflated for the current 1034 // orientation. 1035 getWorkspace().reinflateWidgetsIfNecessary(); 1036 1037 // Process any items that were added while Launcher was away. 1038 InstallShortcutReceiver.disableAndFlushInstallQueue(this); 1039 1040 // Update the voice search button proxy 1041 updateVoiceButtonProxyVisible(false); 1042 1043 // Again, as with the above scenario, it's possible that one or more of the global icons 1044 // were updated in the wrong orientation. 1045 updateGlobalIcons(); 1046 if (DEBUG_RESUME_TIME) { 1047 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime)); 1048 } 1049 1050 if (mWorkspace.getCustomContentCallbacks() != null) { 1051 // If we are resuming and the custom content is the current page, we call onShow(). 1052 // It is also poassible that onShow will instead be called slightly after first layout 1053 // if PagedView#setRestorePage was set to the custom content page in onCreate(). 1054 if (mWorkspace.isOnOrMovingToCustomContent()) { 1055 mWorkspace.getCustomContentCallbacks().onShow(true); 1056 } 1057 } 1058 mWorkspace.updateInteractionForState(); 1059 mWorkspace.onResume(); 1060 1061 PackageInstallerCompat.getInstance(this).onResume(); 1062 } 1063 1064 @Override 1065 protected void onPause() { 1066 // Ensure that items added to Launcher are queued until Launcher returns 1067 InstallShortcutReceiver.enableInstallQueue(); 1068 PackageInstallerCompat.getInstance(this).onPause(); 1069 1070 super.onPause(); 1071 mPaused = true; 1072 mDragController.cancelDrag(); 1073 mDragController.resetLastGestureUpTime(); 1074 1075 // We call onHide() aggressively. The custom content callbacks should be able to 1076 // debounce excess onHide calls. 1077 if (mWorkspace.getCustomContentCallbacks() != null) { 1078 mWorkspace.getCustomContentCallbacks().onHide(); 1079 } 1080 } 1081 1082 QSBScroller mQsbScroller = new QSBScroller() { 1083 int scrollY = 0; 1084 1085 @Override 1086 public void setScrollY(int scroll) { 1087 scrollY = scroll; 1088 1089 if (mWorkspace.isOnOrMovingToCustomContent()) { 1090 mSearchDropTargetBar.setTranslationY(- scrollY); 1091 getQsbBar().setTranslationY(-scrollY); 1092 } 1093 } 1094 }; 1095 1096 public void resetQSBScroll() { 1097 mSearchDropTargetBar.animate().translationY(0).start(); 1098 getQsbBar().animate().translationY(0).start(); 1099 } 1100 1101 public interface CustomContentCallbacks { 1102 // Custom content is completely shown. {@code fromResume} indicates whether this was caused 1103 // by a onResume or by scrolling otherwise. 1104 public void onShow(boolean fromResume); 1105 1106 // Custom content is completely hidden 1107 public void onHide(); 1108 1109 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). 1110 public void onScrollProgressChanged(float progress); 1111 1112 // Indicates whether the user is allowed to scroll away from the custom content. 1113 boolean isScrollingAllowed(); 1114 } 1115 1116 protected boolean hasSettings() { 1117 return false; 1118 } 1119 1120 public interface QSBScroller { 1121 public void setScrollY(int scrollY); 1122 } 1123 1124 public QSBScroller addToCustomContentPage(View customContent, 1125 CustomContentCallbacks callbacks, String description) { 1126 mWorkspace.addToCustomContentPage(customContent, callbacks, description); 1127 return mQsbScroller; 1128 } 1129 1130 // The custom content needs to offset its content to account for the QSB 1131 public int getTopOffsetForCustomContent() { 1132 return mWorkspace.getPaddingTop(); 1133 } 1134 1135 @Override 1136 public Object onRetainNonConfigurationInstance() { 1137 // Flag the loader to stop early before switching 1138 if (mModel.isCurrentCallbacks(this)) { 1139 mModel.stopLoader(); 1140 } 1141 if (mAppsCustomizeContent != null) { 1142 mAppsCustomizeContent.surrender(); 1143 } 1144 return Boolean.TRUE; 1145 } 1146 1147 // We can't hide the IME if it was forced open. So don't bother 1148 @Override 1149 public void onWindowFocusChanged(boolean hasFocus) { 1150 super.onWindowFocusChanged(hasFocus); 1151 mHasFocus = hasFocus; 1152 } 1153 1154 private boolean acceptFilter() { 1155 final InputMethodManager inputManager = (InputMethodManager) 1156 getSystemService(Context.INPUT_METHOD_SERVICE); 1157 return !inputManager.isFullscreenMode(); 1158 } 1159 1160 @Override 1161 public boolean onKeyDown(int keyCode, KeyEvent event) { 1162 final int uniChar = event.getUnicodeChar(); 1163 final boolean handled = super.onKeyDown(keyCode, event); 1164 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); 1165 if (!handled && acceptFilter() && isKeyNotWhitespace) { 1166 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, 1167 keyCode, event); 1168 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { 1169 // something usable has been typed - start a search 1170 // the typed text will be retrieved and cleared by 1171 // showSearchDialog() 1172 // If there are multiple keystrokes before the search dialog takes focus, 1173 // onSearchRequested() will be called for every keystroke, 1174 // but it is idempotent, so it's fine. 1175 return onSearchRequested(); 1176 } 1177 } 1178 1179 // Eat the long press event so the keyboard doesn't come up. 1180 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { 1181 return true; 1182 } 1183 1184 return handled; 1185 } 1186 1187 private String getTypedText() { 1188 return mDefaultKeySsb.toString(); 1189 } 1190 1191 private void clearTypedText() { 1192 mDefaultKeySsb.clear(); 1193 mDefaultKeySsb.clearSpans(); 1194 Selection.setSelection(mDefaultKeySsb, 0); 1195 } 1196 1197 /** 1198 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type 1199 * State 1200 */ 1201 private static State intToState(int stateOrdinal) { 1202 State state = State.WORKSPACE; 1203 final State[] stateValues = State.values(); 1204 for (int i = 0; i < stateValues.length; i++) { 1205 if (stateValues[i].ordinal() == stateOrdinal) { 1206 state = stateValues[i]; 1207 break; 1208 } 1209 } 1210 return state; 1211 } 1212 1213 /** 1214 * Restores the previous state, if it exists. 1215 * 1216 * @param savedState The previous state. 1217 */ 1218 @SuppressWarnings("unchecked") 1219 private void restoreState(Bundle savedState) { 1220 if (savedState == null) { 1221 return; 1222 } 1223 1224 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); 1225 if (state == State.APPS_CUSTOMIZE) { 1226 mOnResumeState = State.APPS_CUSTOMIZE; 1227 } 1228 1229 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, 1230 PagedView.INVALID_RESTORE_PAGE); 1231 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 1232 mWorkspace.setRestorePage(currentScreen); 1233 } 1234 1235 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1); 1236 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); 1237 1238 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) { 1239 mPendingAddInfo.container = pendingAddContainer; 1240 mPendingAddInfo.screenId = pendingAddScreen; 1241 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); 1242 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); 1243 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); 1244 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); 1245 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); 1246 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID); 1247 setWaitingForResult(true); 1248 mRestoring = true; 1249 } 1250 1251 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); 1252 if (renameFolder) { 1253 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); 1254 mFolderInfo = mModel.getFolderById(this, sFolders, id); 1255 mRestoring = true; 1256 } 1257 1258 // Restore the AppsCustomize tab 1259 if (mAppsCustomizeTabHost != null) { 1260 String curTab = savedState.getString("apps_customize_currentTab"); 1261 if (curTab != null) { 1262 mAppsCustomizeTabHost.setContentTypeImmediate( 1263 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab)); 1264 mAppsCustomizeContent.loadAssociatedPages( 1265 mAppsCustomizeContent.getCurrentPage()); 1266 } 1267 1268 int currentIndex = savedState.getInt("apps_customize_currentIndex"); 1269 mAppsCustomizeContent.restorePageForIndex(currentIndex); 1270 } 1271 mItemIdToViewId = (HashMap<Integer, Integer>) 1272 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS); 1273 } 1274 1275 /** 1276 * Finds all the views we need and configure them properly. 1277 */ 1278 private void setupViews() { 1279 final DragController dragController = mDragController; 1280 1281 mLauncherView = findViewById(R.id.launcher); 1282 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator); 1283 mDragLayer = (DragLayer) findViewById(R.id.drag_layer); 1284 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); 1285 mWorkspace.setPageSwitchListener(this); 1286 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator); 1287 1288 mLauncherView.setSystemUiVisibility( 1289 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 1290 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg); 1291 1292 // Setup the drag layer 1293 mDragLayer.setup(this, dragController); 1294 1295 // Setup the hotseat 1296 mHotseat = (Hotseat) findViewById(R.id.hotseat); 1297 if (mHotseat != null) { 1298 mHotseat.setup(this); 1299 mHotseat.setOnLongClickListener(this); 1300 } 1301 1302 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel); 1303 View widgetButton = findViewById(R.id.widget_button); 1304 widgetButton.setOnClickListener(new OnClickListener() { 1305 @Override 1306 public void onClick(View arg0) { 1307 if (!mWorkspace.isSwitchingState()) { 1308 onClickAddWidgetButton(arg0); 1309 } 1310 } 1311 }); 1312 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1313 1314 View wallpaperButton = findViewById(R.id.wallpaper_button); 1315 wallpaperButton.setOnClickListener(new OnClickListener() { 1316 @Override 1317 public void onClick(View arg0) { 1318 if (!mWorkspace.isSwitchingState()) { 1319 onClickWallpaperPicker(arg0); 1320 } 1321 } 1322 }); 1323 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1324 1325 View settingsButton = findViewById(R.id.settings_button); 1326 if (hasSettings()) { 1327 settingsButton.setOnClickListener(new OnClickListener() { 1328 @Override 1329 public void onClick(View arg0) { 1330 if (!mWorkspace.isSwitchingState()) { 1331 onClickSettingsButton(arg0); 1332 } 1333 } 1334 }); 1335 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1336 } else { 1337 settingsButton.setVisibility(View.GONE); 1338 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams(); 1339 lp.gravity = Gravity.END | Gravity.TOP; 1340 widgetButton.requestLayout(); 1341 } 1342 1343 mOverviewPanel.setAlpha(0f); 1344 1345 // Setup the workspace 1346 mWorkspace.setHapticFeedbackEnabled(false); 1347 mWorkspace.setOnLongClickListener(this); 1348 mWorkspace.setup(dragController); 1349 dragController.addDragListener(mWorkspace); 1350 1351 // Get the search/delete bar 1352 mSearchDropTargetBar = (SearchDropTargetBar) 1353 mDragLayer.findViewById(R.id.search_drop_target_bar); 1354 1355 // Setup AppsCustomize 1356 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane); 1357 mAppsCustomizeContent = (AppsCustomizePagedView) 1358 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); 1359 mAppsCustomizeContent.setup(this, dragController); 1360 1361 // Setup the drag controller (drop targets have to be added in reverse order in priority) 1362 dragController.setDragScoller(mWorkspace); 1363 dragController.setScrollView(mDragLayer); 1364 dragController.setMoveTarget(mWorkspace); 1365 dragController.addDropTarget(mWorkspace); 1366 if (mSearchDropTargetBar != null) { 1367 mSearchDropTargetBar.setup(this, dragController); 1368 } 1369 1370 if (getResources().getBoolean(R.bool.debug_memory_enabled)) { 1371 Log.v(TAG, "adding WeightWatcher"); 1372 mWeightWatcher = new WeightWatcher(this); 1373 mWeightWatcher.setAlpha(0.5f); 1374 ((FrameLayout) mLauncherView).addView(mWeightWatcher, 1375 new FrameLayout.LayoutParams( 1376 FrameLayout.LayoutParams.MATCH_PARENT, 1377 FrameLayout.LayoutParams.WRAP_CONTENT, 1378 Gravity.BOTTOM) 1379 ); 1380 1381 boolean show = shouldShowWeightWatcher(); 1382 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); 1383 } 1384 } 1385 1386 /** 1387 * Sets the all apps button. This method is called from {@link Hotseat}. 1388 */ 1389 public void setAllAppsButton(View allAppsButton) { 1390 mAllAppsButton = allAppsButton; 1391 } 1392 1393 public View getAllAppsButton() { 1394 return mAllAppsButton; 1395 } 1396 1397 /** 1398 * Creates a view representing a shortcut. 1399 * 1400 * @param info The data structure describing the shortcut. 1401 * 1402 * @return A View inflated from R.layout.application. 1403 */ 1404 View createShortcut(ShortcutInfo info) { 1405 return createShortcut(R.layout.application, 1406 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); 1407 } 1408 1409 /** 1410 * Creates a view representing a shortcut inflated from the specified resource. 1411 * 1412 * @param layoutResId The id of the XML layout used to create the shortcut. 1413 * @param parent The group the shortcut belongs to. 1414 * @param info The data structure describing the shortcut. 1415 * 1416 * @return A View inflated from layoutResId. 1417 */ 1418 View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { 1419 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false); 1420 favorite.applyFromShortcutInfo(info, mIconCache, true); 1421 favorite.setOnClickListener(this); 1422 favorite.setOnFocusChangeListener(mFocusHandler); 1423 return favorite; 1424 } 1425 1426 /** 1427 * Add a shortcut to the workspace. 1428 * 1429 * @param data The intent describing the shortcut. 1430 * @param cellInfo The position on screen where to create the shortcut. 1431 */ 1432 private void completeAddShortcut(Intent data, long container, long screenId, int cellX, 1433 int cellY) { 1434 int[] cellXY = mTmpAddItemCellCoordinates; 1435 int[] touchXY = mPendingAddInfo.dropPos; 1436 CellLayout layout = getCellLayout(container, screenId); 1437 1438 boolean foundCellSpan = false; 1439 1440 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null); 1441 if (info == null) { 1442 return; 1443 } 1444 final View view = createShortcut(info); 1445 1446 // First we check if we already know the exact location where we want to add this item. 1447 if (cellX >= 0 && cellY >= 0) { 1448 cellXY[0] = cellX; 1449 cellXY[1] = cellY; 1450 foundCellSpan = true; 1451 1452 // If appropriate, either create a folder or add to an existing folder 1453 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, 1454 true, null,null)) { 1455 return; 1456 } 1457 DragObject dragObject = new DragObject(); 1458 dragObject.dragInfo = info; 1459 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, 1460 true)) { 1461 return; 1462 } 1463 } else if (touchXY != null) { 1464 // when dragging and dropping, just find the closest free spot 1465 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY); 1466 foundCellSpan = (result != null); 1467 } else { 1468 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); 1469 } 1470 1471 if (!foundCellSpan) { 1472 showOutOfSpaceMessage(isHotseatLayout(layout)); 1473 return; 1474 } 1475 1476 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false); 1477 1478 if (!mRestoring) { 1479 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, 1480 isWorkspaceLocked()); 1481 } 1482 } 1483 1484 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth, 1485 int minHeight) { 1486 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null); 1487 // We want to account for the extra amount of padding that we are adding to the widget 1488 // to ensure that it gets the full amount of space that it has requested 1489 int requiredWidth = minWidth + padding.left + padding.right; 1490 int requiredHeight = minHeight + padding.top + padding.bottom; 1491 return CellLayout.rectToCell(requiredWidth, requiredHeight, null); 1492 } 1493 1494 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) { 1495 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight); 1496 } 1497 1498 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) { 1499 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight); 1500 } 1501 1502 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) { 1503 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight); 1504 } 1505 1506 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) { 1507 return getSpanForWidget(context, info.componentName, info.minResizeWidth, 1508 info.minResizeHeight); 1509 } 1510 1511 /** 1512 * Add a widget to the workspace. 1513 * 1514 * @param appWidgetId The app widget id 1515 * @param cellInfo The position on screen where to create the widget. 1516 */ 1517 private void completeAddAppWidget(final int appWidgetId, long container, long screenId, 1518 AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) { 1519 if (appWidgetInfo == null) { 1520 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); 1521 } 1522 1523 // Calculate the grid spans needed to fit this widget 1524 CellLayout layout = getCellLayout(container, screenId); 1525 1526 int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo); 1527 int[] spanXY = getSpanForWidget(this, appWidgetInfo); 1528 1529 // Try finding open space on Launcher screen 1530 // We have saved the position to which the widget was dragged-- this really only matters 1531 // if we are placing widgets on a "spring-loaded" screen 1532 int[] cellXY = mTmpAddItemCellCoordinates; 1533 int[] touchXY = mPendingAddInfo.dropPos; 1534 int[] finalSpan = new int[2]; 1535 boolean foundCellSpan = false; 1536 if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) { 1537 cellXY[0] = mPendingAddInfo.cellX; 1538 cellXY[1] = mPendingAddInfo.cellY; 1539 spanXY[0] = mPendingAddInfo.spanX; 1540 spanXY[1] = mPendingAddInfo.spanY; 1541 foundCellSpan = true; 1542 } else if (touchXY != null) { 1543 // when dragging and dropping, just find the closest free spot 1544 int[] result = layout.findNearestVacantArea( 1545 touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0], 1546 spanXY[1], cellXY, finalSpan); 1547 spanXY[0] = finalSpan[0]; 1548 spanXY[1] = finalSpan[1]; 1549 foundCellSpan = (result != null); 1550 } else { 1551 foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]); 1552 } 1553 1554 if (!foundCellSpan) { 1555 if (appWidgetId != -1) { 1556 // Deleting an app widget ID is a void call but writes to disk before returning 1557 // to the caller... 1558 new AsyncTask<Void, Void, Void>() { 1559 public Void doInBackground(Void ... args) { 1560 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 1561 return null; 1562 } 1563 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); 1564 } 1565 showOutOfSpaceMessage(isHotseatLayout(layout)); 1566 return; 1567 } 1568 1569 // Build Launcher-specific widget info and save to database 1570 LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId, 1571 appWidgetInfo.provider); 1572 launcherInfo.spanX = spanXY[0]; 1573 launcherInfo.spanY = spanXY[1]; 1574 launcherInfo.minSpanX = mPendingAddInfo.minSpanX; 1575 launcherInfo.minSpanY = mPendingAddInfo.minSpanY; 1576 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo); 1577 1578 LauncherModel.addItemToDatabase(this, launcherInfo, 1579 container, screenId, cellXY[0], cellXY[1], false); 1580 1581 if (!mRestoring) { 1582 if (hostView == null) { 1583 // Perform actual inflation because we're live 1584 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 1585 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); 1586 } else { 1587 // The AppWidgetHostView has already been inflated and instantiated 1588 launcherInfo.hostView = hostView; 1589 } 1590 1591 launcherInfo.hostView.setTag(launcherInfo); 1592 launcherInfo.hostView.setVisibility(View.VISIBLE); 1593 launcherInfo.notifyWidgetSizeChanged(this); 1594 1595 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1], 1596 launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); 1597 1598 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo); 1599 } 1600 resetAddInfo(); 1601 } 1602 1603 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 1604 @Override 1605 public void onReceive(Context context, Intent intent) { 1606 final String action = intent.getAction(); 1607 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 1608 mUserPresent = false; 1609 mDragLayer.clearAllResizeFrames(); 1610 updateRunning(); 1611 1612 // Reset AllApps to its initial state only if we are not in the middle of 1613 // processing a multi-step drop 1614 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) { 1615 showWorkspace(false); 1616 } 1617 } else if (Intent.ACTION_USER_PRESENT.equals(action)) { 1618 mUserPresent = true; 1619 updateRunning(); 1620 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) { 1621 mModel.resetLoadedState(false, true); 1622 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, 1623 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE); 1624 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) { 1625 mModel.resetLoadedState(false, true); 1626 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, 1627 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE 1628 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); 1629 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) 1630 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 1631 getModel().forceReload(); 1632 } 1633 } 1634 }; 1635 1636 @Override 1637 public void onAttachedToWindow() { 1638 super.onAttachedToWindow(); 1639 1640 // Listen for broadcasts related to user-presence 1641 final IntentFilter filter = new IntentFilter(); 1642 filter.addAction(Intent.ACTION_SCREEN_OFF); 1643 filter.addAction(Intent.ACTION_USER_PRESENT); 1644 // For handling managed profiles 1645 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED); 1646 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED); 1647 if (ENABLE_DEBUG_INTENTS) { 1648 filter.addAction(DebugIntents.DELETE_DATABASE); 1649 filter.addAction(DebugIntents.MIGRATE_DATABASE); 1650 } 1651 registerReceiver(mReceiver, filter); 1652 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); 1653 setupTransparentSystemBarsForLmp(); 1654 mAttached = true; 1655 mVisible = true; 1656 } 1657 1658 /** 1659 * Sets up transparent navigation and status bars in LMP. 1660 * This method is a no-op for other platform versions. 1661 */ 1662 @TargetApi(19) 1663 private void setupTransparentSystemBarsForLmp() { 1664 // TODO(sansid): use the APIs directly when compiling against L sdk. 1665 // Currently we use reflection to access the flags and the API to set the transparency 1666 // on the System bars. 1667 if (Utilities.isLmpOrAbove()) { 1668 try { 1669 getWindow().getAttributes().systemUiVisibility |= 1670 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE 1671 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1672 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 1673 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 1674 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 1675 Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField( 1676 "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS"); 1677 getWindow().addFlags(drawsSysBackgroundsField.getInt(null)); 1678 1679 Method setStatusBarColorMethod = 1680 Window.class.getDeclaredMethod("setStatusBarColor", int.class); 1681 Method setNavigationBarColorMethod = 1682 Window.class.getDeclaredMethod("setNavigationBarColor", int.class); 1683 setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT); 1684 setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT); 1685 } catch (NoSuchFieldException e) { 1686 Log.w(TAG, "NoSuchFieldException while setting up transparent bars"); 1687 } catch (NoSuchMethodException ex) { 1688 Log.w(TAG, "NoSuchMethodException while setting up transparent bars"); 1689 } catch (IllegalAccessException e) { 1690 Log.w(TAG, "IllegalAccessException while setting up transparent bars"); 1691 } catch (IllegalArgumentException e) { 1692 Log.w(TAG, "IllegalArgumentException while setting up transparent bars"); 1693 } catch (InvocationTargetException e) { 1694 Log.w(TAG, "InvocationTargetException while setting up transparent bars"); 1695 } finally {} 1696 } 1697 } 1698 1699 @Override 1700 public void onDetachedFromWindow() { 1701 super.onDetachedFromWindow(); 1702 mVisible = false; 1703 1704 if (mAttached) { 1705 unregisterReceiver(mReceiver); 1706 mAttached = false; 1707 } 1708 updateRunning(); 1709 } 1710 1711 public void onWindowVisibilityChanged(int visibility) { 1712 mVisible = visibility == View.VISIBLE; 1713 updateRunning(); 1714 // The following code used to be in onResume, but it turns out onResume is called when 1715 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged 1716 // is a more appropriate event to handle 1717 if (mVisible) { 1718 mAppsCustomizeTabHost.onWindowVisible(); 1719 if (!mWorkspaceLoading) { 1720 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); 1721 // We want to let Launcher draw itself at least once before we force it to build 1722 // layers on all the workspace pages, so that transitioning to Launcher from other 1723 // apps is nice and speedy. 1724 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() { 1725 private boolean mStarted = false; 1726 public void onDraw() { 1727 if (mStarted) return; 1728 mStarted = true; 1729 // We delay the layer building a bit in order to give 1730 // other message processing a time to run. In particular 1731 // this avoids a delay in hiding the IME if it was 1732 // currently shown, because doing that may involve 1733 // some communication back with the app. 1734 mWorkspace.postDelayed(mBuildLayersRunnable, 500); 1735 final ViewTreeObserver.OnDrawListener listener = this; 1736 mWorkspace.post(new Runnable() { 1737 public void run() { 1738 if (mWorkspace != null && 1739 mWorkspace.getViewTreeObserver() != null) { 1740 mWorkspace.getViewTreeObserver(). 1741 removeOnDrawListener(listener); 1742 } 1743 } 1744 }); 1745 return; 1746 } 1747 }); 1748 } 1749 clearTypedText(); 1750 } 1751 } 1752 1753 private void sendAdvanceMessage(long delay) { 1754 mHandler.removeMessages(ADVANCE_MSG); 1755 Message msg = mHandler.obtainMessage(ADVANCE_MSG); 1756 mHandler.sendMessageDelayed(msg, delay); 1757 mAutoAdvanceSentTime = System.currentTimeMillis(); 1758 } 1759 1760 private void updateRunning() { 1761 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty(); 1762 if (autoAdvanceRunning != mAutoAdvanceRunning) { 1763 mAutoAdvanceRunning = autoAdvanceRunning; 1764 if (autoAdvanceRunning) { 1765 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; 1766 sendAdvanceMessage(delay); 1767 } else { 1768 if (!mWidgetsToAdvance.isEmpty()) { 1769 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - 1770 (System.currentTimeMillis() - mAutoAdvanceSentTime)); 1771 } 1772 mHandler.removeMessages(ADVANCE_MSG); 1773 mHandler.removeMessages(0); // Remove messages sent using postDelayed() 1774 } 1775 } 1776 } 1777 1778 private final Handler mHandler = new Handler() { 1779 @Override 1780 public void handleMessage(Message msg) { 1781 if (msg.what == ADVANCE_MSG) { 1782 int i = 0; 1783 for (View key: mWidgetsToAdvance.keySet()) { 1784 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); 1785 final int delay = mAdvanceStagger * i; 1786 if (v instanceof Advanceable) { 1787 postDelayed(new Runnable() { 1788 public void run() { 1789 ((Advanceable) v).advance(); 1790 } 1791 }, delay); 1792 } 1793 i++; 1794 } 1795 sendAdvanceMessage(mAdvanceInterval); 1796 } 1797 } 1798 }; 1799 1800 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) { 1801 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return; 1802 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId); 1803 if (v instanceof Advanceable) { 1804 mWidgetsToAdvance.put(hostView, appWidgetInfo); 1805 ((Advanceable) v).fyiWillBeAdvancedByHostKThx(); 1806 updateRunning(); 1807 } 1808 } 1809 1810 void removeWidgetToAutoAdvance(View hostView) { 1811 if (mWidgetsToAdvance.containsKey(hostView)) { 1812 mWidgetsToAdvance.remove(hostView); 1813 updateRunning(); 1814 } 1815 } 1816 1817 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { 1818 removeWidgetToAutoAdvance(launcherInfo.hostView); 1819 launcherInfo.hostView = null; 1820 } 1821 1822 void showOutOfSpaceMessage(boolean isHotseatLayout) { 1823 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space); 1824 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show(); 1825 } 1826 1827 public DragLayer getDragLayer() { 1828 return mDragLayer; 1829 } 1830 1831 public Workspace getWorkspace() { 1832 return mWorkspace; 1833 } 1834 1835 public Hotseat getHotseat() { 1836 return mHotseat; 1837 } 1838 1839 public ViewGroup getOverviewPanel() { 1840 return mOverviewPanel; 1841 } 1842 1843 public SearchDropTargetBar getSearchBar() { 1844 return mSearchDropTargetBar; 1845 } 1846 1847 public LauncherAppWidgetHost getAppWidgetHost() { 1848 return mAppWidgetHost; 1849 } 1850 1851 public LauncherModel getModel() { 1852 return mModel; 1853 } 1854 1855 protected SharedPreferences getSharedPrefs() { 1856 return mSharedPrefs; 1857 } 1858 1859 public void closeSystemDialogs() { 1860 getWindow().closeAllPanels(); 1861 1862 // Whatever we were doing is hereby canceled. 1863 setWaitingForResult(false); 1864 } 1865 1866 @Override 1867 protected void onNewIntent(Intent intent) { 1868 long startTime = 0; 1869 if (DEBUG_RESUME_TIME) { 1870 startTime = System.currentTimeMillis(); 1871 } 1872 super.onNewIntent(intent); 1873 1874 // Close the menu 1875 if (Intent.ACTION_MAIN.equals(intent.getAction())) { 1876 // also will cancel mWaitingForResult. 1877 closeSystemDialogs(); 1878 1879 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & 1880 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) 1881 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); 1882 1883 if (mWorkspace == null) { 1884 // Can be cases where mWorkspace is null, this prevents a NPE 1885 return; 1886 } 1887 Folder openFolder = mWorkspace.getOpenFolder(); 1888 // In all these cases, only animate if we're already on home 1889 mWorkspace.exitWidgetResizeMode(); 1890 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() && 1891 openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) { 1892 mWorkspace.moveToDefaultScreen(true); 1893 } 1894 1895 closeFolder(); 1896 exitSpringLoadedDragMode(); 1897 1898 // If we are already on home, then just animate back to the workspace, 1899 // otherwise, just wait until onResume to set the state back to Workspace 1900 if (alreadyOnHome) { 1901 showWorkspace(true); 1902 } else { 1903 mOnResumeState = State.WORKSPACE; 1904 } 1905 1906 final View v = getWindow().peekDecorView(); 1907 if (v != null && v.getWindowToken() != null) { 1908 InputMethodManager imm = (InputMethodManager)getSystemService( 1909 INPUT_METHOD_SERVICE); 1910 imm.hideSoftInputFromWindow(v.getWindowToken(), 0); 1911 } 1912 1913 // Reset the apps customize page 1914 if (!alreadyOnHome && mAppsCustomizeTabHost != null) { 1915 mAppsCustomizeTabHost.reset(); 1916 } 1917 1918 onHomeIntent(); 1919 } 1920 1921 if (DEBUG_RESUME_TIME) { 1922 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime)); 1923 } 1924 } 1925 1926 /** 1927 * Override point for subclasses to prevent movement to the default screen when the home 1928 * button is pressed. Used (for example) in GEL, to prevent movement during a search. 1929 */ 1930 protected boolean shouldMoveToDefaultScreenOnHomeIntent() { 1931 return true; 1932 } 1933 1934 /** 1935 * Override point for subclasses to provide custom behaviour for when a home intent is fired. 1936 */ 1937 protected void onHomeIntent() { 1938 // Do nothing 1939 } 1940 1941 @Override 1942 public void onRestoreInstanceState(Bundle state) { 1943 super.onRestoreInstanceState(state); 1944 for (int page: mSynchronouslyBoundPages) { 1945 mWorkspace.restoreInstanceStateForChild(page); 1946 } 1947 } 1948 1949 @Override 1950 protected void onSaveInstanceState(Bundle outState) { 1951 if (mWorkspace.getChildCount() > 0) { 1952 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, 1953 mWorkspace.getCurrentPageOffsetFromCustomContent()); 1954 } 1955 super.onSaveInstanceState(outState); 1956 1957 outState.putInt(RUNTIME_STATE, mState.ordinal()); 1958 // We close any open folder since it will not be re-opened, and we need to make sure 1959 // this state is reflected. 1960 closeFolder(); 1961 1962 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 && 1963 mWaitingForResult) { 1964 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container); 1965 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId); 1966 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX); 1967 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY); 1968 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX); 1969 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY); 1970 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); 1971 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId); 1972 } 1973 1974 if (mFolderInfo != null && mWaitingForResult) { 1975 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); 1976 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); 1977 } 1978 1979 // Save the current AppsCustomize tab 1980 if (mAppsCustomizeTabHost != null) { 1981 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType(); 1982 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type); 1983 if (currentTabTag != null) { 1984 outState.putString("apps_customize_currentTab", currentTabTag); 1985 } 1986 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex(); 1987 outState.putInt("apps_customize_currentIndex", currentIndex); 1988 } 1989 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId); 1990 } 1991 1992 @Override 1993 public void onDestroy() { 1994 super.onDestroy(); 1995 1996 // Remove all pending runnables 1997 mHandler.removeMessages(ADVANCE_MSG); 1998 mHandler.removeMessages(0); 1999 mWorkspace.removeCallbacks(mBuildLayersRunnable); 2000 2001 // Stop callbacks from LauncherModel 2002 LauncherAppState app = (LauncherAppState.getInstance()); 2003 2004 // It's possible to receive onDestroy after a new Launcher activity has 2005 // been created. In this case, don't interfere with the new Launcher. 2006 if (mModel.isCurrentCallbacks(this)) { 2007 mModel.stopLoader(); 2008 app.setLauncher(null); 2009 } 2010 2011 try { 2012 mAppWidgetHost.stopListening(); 2013 } catch (NullPointerException ex) { 2014 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); 2015 } 2016 mAppWidgetHost = null; 2017 2018 mWidgetsToAdvance.clear(); 2019 2020 TextKeyListener.getInstance().release(); 2021 2022 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace 2023 // to prevent leaking Launcher activities on orientation change. 2024 if (mModel != null) { 2025 mModel.unbindItemInfosAndClearQueuedBindRunnables(); 2026 } 2027 2028 getContentResolver().unregisterContentObserver(mWidgetObserver); 2029 unregisterReceiver(mCloseSystemDialogsReceiver); 2030 2031 mDragLayer.clearAllResizeFrames(); 2032 ((ViewGroup) mWorkspace.getParent()).removeAllViews(); 2033 mWorkspace.removeAllWorkspaceScreens(); 2034 mWorkspace = null; 2035 mDragController = null; 2036 2037 PackageInstallerCompat.getInstance(this).onStop(); 2038 LauncherAnimUtils.onDestroyActivity(); 2039 } 2040 2041 public DragController getDragController() { 2042 return mDragController; 2043 } 2044 2045 @Override 2046 public void startActivityForResult(Intent intent, int requestCode) { 2047 if (requestCode >= 0) { 2048 setWaitingForResult(true); 2049 } 2050 super.startActivityForResult(intent, requestCode); 2051 } 2052 2053 /** 2054 * Indicates that we want global search for this activity by setting the globalSearch 2055 * argument for {@link #startSearch} to true. 2056 */ 2057 @Override 2058 public void startSearch(String initialQuery, boolean selectInitialQuery, 2059 Bundle appSearchData, boolean globalSearch) { 2060 2061 showWorkspace(true); 2062 2063 if (initialQuery == null) { 2064 // Use any text typed in the launcher as the initial query 2065 initialQuery = getTypedText(); 2066 } 2067 if (appSearchData == null) { 2068 appSearchData = new Bundle(); 2069 appSearchData.putString("source", "launcher-search"); 2070 } 2071 Rect sourceBounds = new Rect(); 2072 if (mSearchDropTargetBar != null) { 2073 sourceBounds = mSearchDropTargetBar.getSearchBarBounds(); 2074 } 2075 2076 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery, 2077 appSearchData, sourceBounds); 2078 if (clearTextImmediately) { 2079 clearTypedText(); 2080 } 2081 } 2082 2083 /** 2084 * Start a text search. 2085 * 2086 * @return {@code true} if the search will start immediately, so any further keypresses 2087 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue 2088 * to buffer keypresses. 2089 */ 2090 public boolean startSearch(String initialQuery, 2091 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { 2092 startGlobalSearch(initialQuery, selectInitialQuery, 2093 appSearchData, sourceBounds); 2094 return false; 2095 } 2096 2097 /** 2098 * Starts the global search activity. This code is a copied from SearchManager 2099 */ 2100 private void startGlobalSearch(String initialQuery, 2101 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { 2102 final SearchManager searchManager = 2103 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 2104 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity(); 2105 if (globalSearchActivity == null) { 2106 Log.w(TAG, "No global search activity found."); 2107 return; 2108 } 2109 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); 2110 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2111 intent.setComponent(globalSearchActivity); 2112 // Make sure that we have a Bundle to put source in 2113 if (appSearchData == null) { 2114 appSearchData = new Bundle(); 2115 } else { 2116 appSearchData = new Bundle(appSearchData); 2117 } 2118 // Set source to package name of app that starts global search, if not set already. 2119 if (!appSearchData.containsKey("source")) { 2120 appSearchData.putString("source", getPackageName()); 2121 } 2122 intent.putExtra(SearchManager.APP_DATA, appSearchData); 2123 if (!TextUtils.isEmpty(initialQuery)) { 2124 intent.putExtra(SearchManager.QUERY, initialQuery); 2125 } 2126 if (selectInitialQuery) { 2127 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery); 2128 } 2129 intent.setSourceBounds(sourceBounds); 2130 try { 2131 startActivity(intent); 2132 } catch (ActivityNotFoundException ex) { 2133 Log.e(TAG, "Global search activity not found: " + globalSearchActivity); 2134 } 2135 } 2136 2137 public boolean isOnCustomContent() { 2138 return mWorkspace.isOnOrMovingToCustomContent(); 2139 } 2140 2141 @Override 2142 public boolean onPrepareOptionsMenu(Menu menu) { 2143 super.onPrepareOptionsMenu(menu); 2144 if (!isOnCustomContent()) { 2145 // Close any open folders 2146 closeFolder(); 2147 // Stop resizing any widgets 2148 mWorkspace.exitWidgetResizeMode(); 2149 if (!mWorkspace.isInOverviewMode()) { 2150 // Show the overview mode 2151 showOverviewMode(true); 2152 } else { 2153 showWorkspace(true); 2154 } 2155 } 2156 return false; 2157 } 2158 2159 @Override 2160 public boolean onSearchRequested() { 2161 startSearch(null, false, null, true); 2162 // Use a custom animation for launching search 2163 return true; 2164 } 2165 2166 public boolean isWorkspaceLocked() { 2167 return mWorkspaceLoading || mWaitingForResult; 2168 } 2169 2170 public boolean isWorkspaceLoading() { 2171 return mWorkspaceLoading; 2172 } 2173 2174 private void setWorkspaceLoading(boolean value) { 2175 boolean isLocked = isWorkspaceLocked(); 2176 mWorkspaceLoading = value; 2177 if (isLocked != isWorkspaceLocked()) { 2178 onWorkspaceLockedChanged(); 2179 } 2180 } 2181 2182 private void setWaitingForResult(boolean value) { 2183 boolean isLocked = isWorkspaceLocked(); 2184 mWaitingForResult = value; 2185 if (isLocked != isWorkspaceLocked()) { 2186 onWorkspaceLockedChanged(); 2187 } 2188 } 2189 2190 protected void onWorkspaceLockedChanged() { } 2191 2192 private void resetAddInfo() { 2193 mPendingAddInfo.container = ItemInfo.NO_ID; 2194 mPendingAddInfo.screenId = -1; 2195 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; 2196 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; 2197 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1; 2198 mPendingAddInfo.dropPos = null; 2199 } 2200 2201 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, 2202 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) { 2203 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0); 2204 } 2205 2206 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, 2207 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int 2208 delay) { 2209 if (appWidgetInfo.configure != null) { 2210 mPendingAddWidgetInfo = appWidgetInfo; 2211 mPendingAddWidgetId = appWidgetId; 2212 2213 // Launch over to configure widget, if needed 2214 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this, 2215 mAppWidgetHost, REQUEST_CREATE_APPWIDGET); 2216 2217 } else { 2218 // Otherwise just add it 2219 Runnable onComplete = new Runnable() { 2220 @Override 2221 public void run() { 2222 // Exit spring loaded mode if necessary after adding the widget 2223 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, 2224 null); 2225 } 2226 }; 2227 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget, 2228 appWidgetInfo); 2229 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); 2230 } 2231 } 2232 2233 protected void moveToCustomContentScreen(boolean animate) { 2234 // Close any folders that may be open. 2235 closeFolder(); 2236 mWorkspace.moveToCustomContentScreen(animate); 2237 } 2238 /** 2239 * Process a shortcut drop. 2240 * 2241 * @param componentName The name of the component 2242 * @param screenId The ID of the screen where it should be added 2243 * @param cell The cell it should be added to, optional 2244 * @param position The location on the screen where it was dropped, optional 2245 */ 2246 void processShortcutFromDrop(ComponentName componentName, long container, long screenId, 2247 int[] cell, int[] loc) { 2248 resetAddInfo(); 2249 mPendingAddInfo.container = container; 2250 mPendingAddInfo.screenId = screenId; 2251 mPendingAddInfo.dropPos = loc; 2252 2253 if (cell != null) { 2254 mPendingAddInfo.cellX = cell[0]; 2255 mPendingAddInfo.cellY = cell[1]; 2256 } 2257 2258 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 2259 createShortcutIntent.setComponent(componentName); 2260 processShortcut(createShortcutIntent); 2261 } 2262 2263 /** 2264 * Process a widget drop. 2265 * 2266 * @param info The PendingAppWidgetInfo of the widget being added. 2267 * @param screenId The ID of the screen where it should be added 2268 * @param cell The cell it should be added to, optional 2269 * @param position The location on the screen where it was dropped, optional 2270 */ 2271 void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId, 2272 int[] cell, int[] span, int[] loc) { 2273 resetAddInfo(); 2274 mPendingAddInfo.container = info.container = container; 2275 mPendingAddInfo.screenId = info.screenId = screenId; 2276 mPendingAddInfo.dropPos = loc; 2277 mPendingAddInfo.minSpanX = info.minSpanX; 2278 mPendingAddInfo.minSpanY = info.minSpanY; 2279 2280 if (cell != null) { 2281 mPendingAddInfo.cellX = cell[0]; 2282 mPendingAddInfo.cellY = cell[1]; 2283 } 2284 if (span != null) { 2285 mPendingAddInfo.spanX = span[0]; 2286 mPendingAddInfo.spanY = span[1]; 2287 } 2288 2289 AppWidgetHostView hostView = info.boundWidget; 2290 int appWidgetId; 2291 if (hostView != null) { 2292 appWidgetId = hostView.getAppWidgetId(); 2293 addAppWidgetImpl(appWidgetId, info, hostView, info.info); 2294 } else { 2295 // In this case, we either need to start an activity to get permission to bind 2296 // the widget, or we need to start an activity to configure the widget, or both. 2297 appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 2298 Bundle options = info.bindOptions; 2299 2300 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 2301 appWidgetId, info.info, options); 2302 if (success) { 2303 addAppWidgetImpl(appWidgetId, info, null, info.info); 2304 } else { 2305 mPendingAddWidgetInfo = info.info; 2306 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); 2307 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 2308 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName); 2309 mAppWidgetManager.getUser(mPendingAddWidgetInfo) 2310 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE); 2311 // TODO: we need to make sure that this accounts for the options bundle. 2312 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); 2313 startActivityForResult(intent, REQUEST_BIND_APPWIDGET); 2314 } 2315 } 2316 } 2317 2318 void processShortcut(Intent intent) { 2319 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT); 2320 } 2321 2322 void processWallpaper(Intent intent) { 2323 startActivityForResult(intent, REQUEST_PICK_WALLPAPER); 2324 } 2325 2326 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, 2327 int cellY) { 2328 final FolderInfo folderInfo = new FolderInfo(); 2329 folderInfo.title = getText(R.string.folder_name); 2330 2331 // Update the model 2332 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY, 2333 false); 2334 sFolders.put(folderInfo.id, folderInfo); 2335 2336 // Create the view 2337 FolderIcon newFolder = 2338 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache); 2339 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1, 2340 isWorkspaceLocked()); 2341 // Force measure the new folder icon 2342 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder); 2343 parent.getShortcutsAndWidgets().measureChild(newFolder); 2344 return newFolder; 2345 } 2346 2347 void removeFolder(FolderInfo folder) { 2348 sFolders.remove(folder.id); 2349 } 2350 2351 protected ComponentName getWallpaperPickerComponent() { 2352 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName()); 2353 } 2354 2355 /** 2356 * Registers various content observers. The current implementation registers 2357 * only a favorites observer to keep track of the favorites applications. 2358 */ 2359 private void registerContentObservers() { 2360 ContentResolver resolver = getContentResolver(); 2361 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, 2362 true, mWidgetObserver); 2363 } 2364 2365 @Override 2366 public boolean dispatchKeyEvent(KeyEvent event) { 2367 if (event.getAction() == KeyEvent.ACTION_DOWN) { 2368 switch (event.getKeyCode()) { 2369 case KeyEvent.KEYCODE_HOME: 2370 return true; 2371 case KeyEvent.KEYCODE_VOLUME_DOWN: 2372 if (isPropertyEnabled(DUMP_STATE_PROPERTY)) { 2373 dumpState(); 2374 return true; 2375 } 2376 break; 2377 } 2378 } else if (event.getAction() == KeyEvent.ACTION_UP) { 2379 switch (event.getKeyCode()) { 2380 case KeyEvent.KEYCODE_HOME: 2381 return true; 2382 } 2383 } 2384 2385 return super.dispatchKeyEvent(event); 2386 } 2387 2388 @Override 2389 public void onBackPressed() { 2390 if (isAllAppsVisible()) { 2391 if (mAppsCustomizeContent.getContentType() == 2392 AppsCustomizePagedView.ContentType.Applications) { 2393 showWorkspace(true); 2394 } else { 2395 showOverviewMode(true); 2396 } 2397 } else if (mWorkspace.isInOverviewMode()) { 2398 mWorkspace.exitOverviewMode(true); 2399 } else if (mWorkspace.getOpenFolder() != null) { 2400 Folder openFolder = mWorkspace.getOpenFolder(); 2401 if (openFolder.isEditingName()) { 2402 openFolder.dismissEditingName(); 2403 } else { 2404 closeFolder(); 2405 } 2406 } else { 2407 mWorkspace.exitWidgetResizeMode(); 2408 2409 // Back button is a no-op here, but give at least some feedback for the button press 2410 mWorkspace.showOutlinesTemporarily(); 2411 } 2412 } 2413 2414 /** 2415 * Re-listen when widgets are reset. 2416 */ 2417 private void onAppWidgetReset() { 2418 if (mAppWidgetHost != null) { 2419 mAppWidgetHost.startListening(); 2420 } 2421 } 2422 2423 /** 2424 * Launches the intent referred by the clicked shortcut. 2425 * 2426 * @param v The view representing the clicked shortcut. 2427 */ 2428 public void onClick(View v) { 2429 // Make sure that rogue clicks don't get through while allapps is launching, or after the 2430 // view has detached (it's possible for this to happen if the view is removed mid touch). 2431 if (v.getWindowToken() == null) { 2432 return; 2433 } 2434 2435 if (!mWorkspace.isFinishedSwitchingState()) { 2436 return; 2437 } 2438 2439 if (v instanceof Workspace) { 2440 if (mWorkspace.isInOverviewMode()) { 2441 mWorkspace.exitOverviewMode(true); 2442 } 2443 return; 2444 } 2445 2446 if (v instanceof CellLayout) { 2447 if (mWorkspace.isInOverviewMode()) { 2448 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true); 2449 } 2450 } 2451 2452 Object tag = v.getTag(); 2453 if (tag instanceof ShortcutInfo) { 2454 onClickAppShortcut(v); 2455 } else if (tag instanceof FolderInfo) { 2456 if (v instanceof FolderIcon) { 2457 onClickFolderIcon(v); 2458 } 2459 } else if (v == mAllAppsButton) { 2460 onClickAllAppsButton(v); 2461 } else if (tag instanceof AppInfo) { 2462 startAppShortcutOrInfoActivity(v); 2463 } else if (tag instanceof LauncherAppWidgetInfo) { 2464 if (v instanceof PendingAppWidgetHostView) { 2465 onClickPendingWidget((PendingAppWidgetHostView) v); 2466 } 2467 } 2468 } 2469 2470 public void onClickPagedViewIcon(View v) { 2471 startAppShortcutOrInfoActivity(v); 2472 } 2473 2474 public boolean onTouch(View v, MotionEvent event) { 2475 return false; 2476 } 2477 2478 /** 2479 * Event handler for the app widget view which has not fully restored. 2480 */ 2481 public void onClickPendingWidget(final PendingAppWidgetHostView v) { 2482 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); 2483 if (v.isReadyForClickSetup()) { 2484 int widgetId = info.appWidgetId; 2485 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId); 2486 if (appWidgetInfo != null) { 2487 mPendingAddWidgetInfo = appWidgetInfo; 2488 mPendingAddInfo.copyFrom(info); 2489 mPendingAddWidgetId = widgetId; 2490 2491 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo, 2492 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET); 2493 } 2494 } else if (info.installProgress < 0) { 2495 // The install has not been queued 2496 final String packageName = info.providerName.getPackageName(); 2497 showBrokenAppInstallDialog(packageName, 2498 new DialogInterface.OnClickListener() { 2499 public void onClick(DialogInterface dialog, int id) { 2500 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); 2501 } 2502 }); 2503 } else { 2504 // Download has started. 2505 final String packageName = info.providerName.getPackageName(); 2506 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); 2507 } 2508 } 2509 2510 /** 2511 * Event handler for the search button 2512 * 2513 * @param v The view that was clicked. 2514 */ 2515 public void onClickSearchButton(View v) { 2516 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2517 2518 onSearchRequested(); 2519 } 2520 2521 /** 2522 * Event handler for the voice button 2523 * 2524 * @param v The view that was clicked. 2525 */ 2526 public void onClickVoiceButton(View v) { 2527 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2528 2529 startVoice(); 2530 } 2531 2532 public void startVoice() { 2533 try { 2534 final SearchManager searchManager = 2535 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 2536 ComponentName activityName = searchManager.getGlobalSearchActivity(); 2537 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 2538 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2539 if (activityName != null) { 2540 intent.setPackage(activityName.getPackageName()); 2541 } 2542 startActivity(null, intent, "onClickVoiceButton"); 2543 } catch (ActivityNotFoundException e) { 2544 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 2545 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2546 startActivitySafely(null, intent, "onClickVoiceButton"); 2547 } 2548 } 2549 2550 /** 2551 * Event handler for the "grid" button that appears on the home screen, which 2552 * enters all apps mode. 2553 * 2554 * @param v The view that was clicked. 2555 */ 2556 protected void onClickAllAppsButton(View v) { 2557 if (LOGD) Log.d(TAG, "onClickAllAppsButton"); 2558 if (isAllAppsVisible()) { 2559 showWorkspace(true); 2560 } else { 2561 showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false); 2562 } 2563 } 2564 2565 private void showBrokenAppInstallDialog(final String packageName, 2566 DialogInterface.OnClickListener onSearchClickListener) { 2567 new AlertDialog.Builder(new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault)) 2568 .setTitle(R.string.abandoned_promises_title) 2569 .setMessage(R.string.abandoned_promise_explanation) 2570 .setPositiveButton(R.string.abandoned_search, onSearchClickListener) 2571 .setNeutralButton(R.string.abandoned_clean_this, 2572 new DialogInterface.OnClickListener() { 2573 public void onClick(DialogInterface dialog, int id) { 2574 final UserHandleCompat user = UserHandleCompat.myUserHandle(); 2575 mWorkspace.removeAbandonedPromise(packageName, user); 2576 } 2577 }) 2578 .create().show(); 2579 return; 2580 } 2581 2582 /** 2583 * Event handler for an app shortcut click. 2584 * 2585 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}. 2586 */ 2587 protected void onClickAppShortcut(final View v) { 2588 if (LOGD) Log.d(TAG, "onClickAppShortcut"); 2589 Object tag = v.getTag(); 2590 if (!(tag instanceof ShortcutInfo)) { 2591 throw new IllegalArgumentException("Input must be a Shortcut"); 2592 } 2593 2594 // Open shortcut 2595 final ShortcutInfo shortcut = (ShortcutInfo) tag; 2596 final Intent intent = shortcut.intent; 2597 2598 // Check for special shortcuts 2599 if (intent.getComponent() != null) { 2600 final String shortcutClass = intent.getComponent().getClassName(); 2601 2602 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) { 2603 MemoryDumpActivity.startDump(this); 2604 return; 2605 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) { 2606 toggleShowWeightWatcher(); 2607 return; 2608 } 2609 } 2610 2611 // Check for abandoned promise 2612 if ((v instanceof BubbleTextView) 2613 && shortcut.isPromise() 2614 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) { 2615 showBrokenAppInstallDialog( 2616 shortcut.getTargetComponent().getPackageName(), 2617 new DialogInterface.OnClickListener() { 2618 public void onClick(DialogInterface dialog, int id) { 2619 startAppShortcutOrInfoActivity(v); 2620 } 2621 }); 2622 return; 2623 } 2624 2625 // Start activities 2626 startAppShortcutOrInfoActivity(v); 2627 } 2628 2629 private void startAppShortcutOrInfoActivity(View v) { 2630 Object tag = v.getTag(); 2631 final ShortcutInfo shortcut; 2632 final Intent intent; 2633 if (tag instanceof ShortcutInfo) { 2634 shortcut = (ShortcutInfo) tag; 2635 intent = shortcut.intent; 2636 int[] pos = new int[2]; 2637 v.getLocationOnScreen(pos); 2638 intent.setSourceBounds(new Rect(pos[0], pos[1], 2639 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 2640 2641 } else if (tag instanceof AppInfo) { 2642 shortcut = null; 2643 intent = ((AppInfo) tag).intent; 2644 } else { 2645 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo"); 2646 } 2647 2648 boolean success = startActivitySafely(v, intent, tag); 2649 mStats.recordLaunch(intent, shortcut); 2650 2651 if (success && v instanceof BubbleTextView) { 2652 mWaitingForResume = (BubbleTextView) v; 2653 mWaitingForResume.setStayPressed(true); 2654 } 2655 } 2656 2657 /** 2658 * Event handler for a folder icon click. 2659 * 2660 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}. 2661 */ 2662 protected void onClickFolderIcon(View v) { 2663 if (LOGD) Log.d(TAG, "onClickFolder"); 2664 if (!(v instanceof FolderIcon)){ 2665 throw new IllegalArgumentException("Input must be a FolderIcon"); 2666 } 2667 2668 FolderIcon folderIcon = (FolderIcon) v; 2669 final FolderInfo info = folderIcon.getFolderInfo(); 2670 Folder openFolder = mWorkspace.getFolderForTag(info); 2671 2672 // If the folder info reports that the associated folder is open, then verify that 2673 // it is actually opened. There have been a few instances where this gets out of sync. 2674 if (info.opened && openFolder == null) { 2675 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " 2676 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")"); 2677 info.opened = false; 2678 } 2679 2680 if (!info.opened && !folderIcon.getFolder().isDestroyed()) { 2681 // Close any open folder 2682 closeFolder(); 2683 // Open the requested folder 2684 openFolder(folderIcon); 2685 } else { 2686 // Find the open folder... 2687 int folderScreen; 2688 if (openFolder != null) { 2689 folderScreen = mWorkspace.getPageForView(openFolder); 2690 // .. and close it 2691 closeFolder(openFolder); 2692 if (folderScreen != mWorkspace.getCurrentPage()) { 2693 // Close any folder open on the current screen 2694 closeFolder(); 2695 // Pull the folder onto this screen 2696 openFolder(folderIcon); 2697 } 2698 } 2699 } 2700 } 2701 2702 /** 2703 * Event handler for the (Add) Widgets button that appears after a long press 2704 * on the home screen. 2705 */ 2706 protected void onClickAddWidgetButton(View view) { 2707 if (LOGD) Log.d(TAG, "onClickAddWidgetButton"); 2708 showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true); 2709 } 2710 2711 /** 2712 * Event handler for the wallpaper picker button that appears after a long press 2713 * on the home screen. 2714 */ 2715 protected void onClickWallpaperPicker(View v) { 2716 if (LOGD) Log.d(TAG, "onClickWallpaperPicker"); 2717 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); 2718 pickWallpaper.setComponent(getWallpaperPickerComponent()); 2719 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER); 2720 } 2721 2722 /** 2723 * Event handler for a click on the settings button that appears after a long press 2724 * on the home screen. 2725 */ 2726 protected void onClickSettingsButton(View v) { 2727 if (LOGD) Log.d(TAG, "onClickSettingsButton"); 2728 } 2729 2730 public void onTouchDownAllAppsButton(View v) { 2731 // Provide the same haptic feedback that the system offers for virtual keys. 2732 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2733 } 2734 2735 public void performHapticFeedbackOnTouchDown(View v) { 2736 // Provide the same haptic feedback that the system offers for virtual keys. 2737 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2738 } 2739 2740 public View.OnTouchListener getHapticFeedbackTouchListener() { 2741 if (mHapticFeedbackTouchListener == null) { 2742 mHapticFeedbackTouchListener = new View.OnTouchListener() { 2743 @Override 2744 public boolean onTouch(View v, MotionEvent event) { 2745 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { 2746 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2747 } 2748 return false; 2749 } 2750 }; 2751 } 2752 return mHapticFeedbackTouchListener; 2753 } 2754 2755 public void onDragStarted(View view) {} 2756 2757 /** 2758 * Called when the user stops interacting with the launcher. 2759 * This implies that the user is now on the homescreen and is not doing housekeeping. 2760 */ 2761 protected void onInteractionEnd() {} 2762 2763 /** 2764 * Called when the user starts interacting with the launcher. 2765 * The possible interactions are: 2766 * - open all apps 2767 * - reorder an app shortcut, or a widget 2768 * - open the overview mode. 2769 * This is a good time to stop doing things that only make sense 2770 * when the user is on the homescreen and not doing housekeeping. 2771 */ 2772 protected void onInteractionBegin() {} 2773 2774 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) { 2775 String packageName = componentName.getPackageName(); 2776 try { 2777 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); 2778 UserManagerCompat userManager = UserManagerCompat.getInstance(this); 2779 launcherApps.showAppDetailsForProfile(componentName, user); 2780 } catch (SecurityException e) { 2781 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2782 Log.e(TAG, "Launcher does not have permission to launch settings"); 2783 } catch (ActivityNotFoundException e) { 2784 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2785 Log.e(TAG, "Unable to launch settings"); 2786 } 2787 } 2788 2789 // returns true if the activity was started 2790 boolean startApplicationUninstallActivity(ComponentName componentName, int flags, 2791 UserHandleCompat user) { 2792 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) { 2793 // System applications cannot be installed. For now, show a toast explaining that. 2794 // We may give them the option of disabling apps this way. 2795 int messageId = R.string.uninstall_system_app_text; 2796 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show(); 2797 return false; 2798 } else { 2799 String packageName = componentName.getPackageName(); 2800 String className = componentName.getClassName(); 2801 Intent intent = new Intent( 2802 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className)); 2803 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 2804 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 2805 if (user != null) { 2806 user.addToIntent(intent, Intent.EXTRA_USER); 2807 } 2808 startActivity(intent); 2809 return true; 2810 } 2811 } 2812 2813 boolean startActivity(View v, Intent intent, Object tag) { 2814 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2815 try { 2816 // Only launch using the new animation if the shortcut has not opted out (this is a 2817 // private contract between launcher and may be ignored in the future). 2818 boolean useLaunchAnimation = (v != null) && 2819 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); 2820 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); 2821 UserManagerCompat userManager = UserManagerCompat.getInstance(this); 2822 2823 UserHandleCompat user = null; 2824 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) { 2825 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1); 2826 user = userManager.getUserForSerialNumber(serialNumber); 2827 } 2828 2829 Bundle optsBundle = null; 2830 if (useLaunchAnimation) { 2831 ActivityOptions opts = Utilities.isLmpOrAbove() ? 2832 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim) : 2833 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); 2834 optsBundle = opts.toBundle(); 2835 } 2836 2837 if (user == null || user.equals(UserHandleCompat.myUserHandle())) { 2838 // Could be launching some bookkeeping activity 2839 startActivity(intent, optsBundle); 2840 } else { 2841 // TODO Component can be null when shortcuts are supported for secondary user 2842 launcherApps.startActivityForProfile(intent.getComponent(), user, 2843 intent.getSourceBounds(), optsBundle); 2844 } 2845 return true; 2846 } catch (SecurityException e) { 2847 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2848 Log.e(TAG, "Launcher does not have the permission to launch " + intent + 2849 ". Make sure to create a MAIN intent-filter for the corresponding activity " + 2850 "or use the exported attribute for this activity. " 2851 + "tag="+ tag + " intent=" + intent, e); 2852 } 2853 return false; 2854 } 2855 2856 boolean startActivitySafely(View v, Intent intent, Object tag) { 2857 boolean success = false; 2858 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { 2859 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); 2860 return false; 2861 } 2862 try { 2863 success = startActivity(v, intent, tag); 2864 } catch (ActivityNotFoundException e) { 2865 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2866 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); 2867 } 2868 return success; 2869 } 2870 2871 /** 2872 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView 2873 * in the DragLayer in the exact absolute location of the original FolderIcon. 2874 */ 2875 private void copyFolderIconToImage(FolderIcon fi) { 2876 final int width = fi.getMeasuredWidth(); 2877 final int height = fi.getMeasuredHeight(); 2878 2879 // Lazy load ImageView, Bitmap and Canvas 2880 if (mFolderIconImageView == null) { 2881 mFolderIconImageView = new ImageView(this); 2882 } 2883 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width || 2884 mFolderIconBitmap.getHeight() != height) { 2885 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 2886 mFolderIconCanvas = new Canvas(mFolderIconBitmap); 2887 } 2888 2889 DragLayer.LayoutParams lp; 2890 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) { 2891 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams(); 2892 } else { 2893 lp = new DragLayer.LayoutParams(width, height); 2894 } 2895 2896 // The layout from which the folder is being opened may be scaled, adjust the starting 2897 // view size by this scale factor. 2898 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation); 2899 lp.customPosition = true; 2900 lp.x = mRectForFolderAnimation.left; 2901 lp.y = mRectForFolderAnimation.top; 2902 lp.width = (int) (scale * width); 2903 lp.height = (int) (scale * height); 2904 2905 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 2906 fi.draw(mFolderIconCanvas); 2907 mFolderIconImageView.setImageBitmap(mFolderIconBitmap); 2908 if (fi.getFolder() != null) { 2909 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation()); 2910 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation()); 2911 } 2912 // Just in case this image view is still in the drag layer from a previous animation, 2913 // we remove it and re-add it. 2914 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) { 2915 mDragLayer.removeView(mFolderIconImageView); 2916 } 2917 mDragLayer.addView(mFolderIconImageView, lp); 2918 if (fi.getFolder() != null) { 2919 fi.getFolder().bringToFront(); 2920 } 2921 } 2922 2923 private void growAndFadeOutFolderIcon(FolderIcon fi) { 2924 if (fi == null) return; 2925 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); 2926 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f); 2927 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f); 2928 2929 FolderInfo info = (FolderInfo) fi.getTag(); 2930 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2931 CellLayout cl = (CellLayout) fi.getParent().getParent(); 2932 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams(); 2933 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); 2934 } 2935 2936 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original 2937 copyFolderIconToImage(fi); 2938 fi.setVisibility(View.INVISIBLE); 2939 2940 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, 2941 scaleX, scaleY); 2942 if (Utilities.isLmpOrAbove()) { 2943 oa.setInterpolator(new LogDecelerateInterpolator(100, 0)); 2944 } 2945 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); 2946 oa.start(); 2947 } 2948 2949 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) { 2950 if (fi == null) return; 2951 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); 2952 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); 2953 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); 2954 2955 final CellLayout cl = (CellLayout) fi.getParent().getParent(); 2956 2957 // We remove and re-draw the FolderIcon in-case it has changed 2958 mDragLayer.removeView(mFolderIconImageView); 2959 copyFolderIconToImage(fi); 2960 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, 2961 scaleX, scaleY); 2962 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); 2963 oa.addListener(new AnimatorListenerAdapter() { 2964 @Override 2965 public void onAnimationEnd(Animator animation) { 2966 if (cl != null) { 2967 cl.clearFolderLeaveBehind(); 2968 // Remove the ImageView copy of the FolderIcon and make the original visible. 2969 mDragLayer.removeView(mFolderIconImageView); 2970 fi.setVisibility(View.VISIBLE); 2971 } 2972 } 2973 }); 2974 oa.start(); 2975 } 2976 2977 /** 2978 * Opens the user folder described by the specified tag. The opening of the folder 2979 * is animated relative to the specified View. If the View is null, no animation 2980 * is played. 2981 * 2982 * @param folderInfo The FolderInfo describing the folder to open. 2983 */ 2984 public void openFolder(FolderIcon folderIcon) { 2985 Folder folder = folderIcon.getFolder(); 2986 FolderInfo info = folder.mInfo; 2987 2988 info.opened = true; 2989 2990 // Just verify that the folder hasn't already been added to the DragLayer. 2991 // There was a one-off crash where the folder had a parent already. 2992 if (folder.getParent() == null) { 2993 mDragLayer.addView(folder); 2994 mDragController.addDropTarget((DropTarget) folder); 2995 } else { 2996 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + 2997 folder.getParent() + ")."); 2998 } 2999 folder.animateOpen(); 3000 growAndFadeOutFolderIcon(folderIcon); 3001 3002 // Notify the accessibility manager that this folder "window" has appeared and occluded 3003 // the workspace items 3004 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 3005 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 3006 } 3007 3008 public void closeFolder() { 3009 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null; 3010 if (folder != null) { 3011 if (folder.isEditingName()) { 3012 folder.dismissEditingName(); 3013 } 3014 closeFolder(folder); 3015 } 3016 } 3017 3018 void closeFolder(Folder folder) { 3019 folder.getInfo().opened = false; 3020 3021 ViewGroup parent = (ViewGroup) folder.getParent().getParent(); 3022 if (parent != null) { 3023 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo); 3024 shrinkAndFadeInFolderIcon(fi); 3025 } 3026 folder.animateClosed(); 3027 3028 // Notify the accessibility manager that this folder "window" has disappeard and no 3029 // longer occludeds the workspace items 3030 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 3031 } 3032 3033 public boolean onLongClick(View v) { 3034 if (!isDraggingEnabled()) return false; 3035 if (isWorkspaceLocked()) return false; 3036 if (mState != State.WORKSPACE) return false; 3037 3038 if (v instanceof Workspace) { 3039 if (!mWorkspace.isInOverviewMode()) { 3040 if (mWorkspace.enterOverviewMode()) { 3041 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 3042 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 3043 return true; 3044 } else { 3045 return false; 3046 } 3047 } else { 3048 return false; 3049 } 3050 } 3051 3052 CellLayout.CellInfo longClickCellInfo = null; 3053 View itemUnderLongClick = null; 3054 if (v.getTag() instanceof ItemInfo) { 3055 ItemInfo info = (ItemInfo) v.getTag(); 3056 longClickCellInfo = new CellLayout.CellInfo(v, info);; 3057 itemUnderLongClick = longClickCellInfo.cell; 3058 resetAddInfo(); 3059 } 3060 3061 // The hotseat touch handling does not go through Workspace, and we always allow long press 3062 // on hotseat items. 3063 final boolean inHotseat = isHotseatLayout(v); 3064 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress(); 3065 if (allowLongPress && !mDragController.isDragging()) { 3066 if (itemUnderLongClick == null) { 3067 // User long pressed on empty space 3068 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 3069 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 3070 if (mWorkspace.isInOverviewMode()) { 3071 mWorkspace.startReordering(v); 3072 } else { 3073 mWorkspace.enterOverviewMode(); 3074 } 3075 } else { 3076 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank( 3077 mHotseat.getOrderInHotseat( 3078 longClickCellInfo.cellX, 3079 longClickCellInfo.cellY)); 3080 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) { 3081 // User long pressed on an item 3082 mWorkspace.startDrag(longClickCellInfo); 3083 } 3084 } 3085 } 3086 return true; 3087 } 3088 3089 boolean isHotseatLayout(View layout) { 3090 return mHotseat != null && layout != null && 3091 (layout instanceof CellLayout) && (layout == mHotseat.getLayout()); 3092 } 3093 3094 /** 3095 * Returns the CellLayout of the specified container at the specified screen. 3096 */ 3097 CellLayout getCellLayout(long container, long screenId) { 3098 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 3099 if (mHotseat != null) { 3100 return mHotseat.getLayout(); 3101 } else { 3102 return null; 3103 } 3104 } else { 3105 return (CellLayout) mWorkspace.getScreenWithId(screenId); 3106 } 3107 } 3108 3109 public boolean isAllAppsVisible() { 3110 return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE); 3111 } 3112 3113 private void setWorkspaceBackground(boolean workspace) { 3114 mLauncherView.setBackground(workspace ? 3115 mWorkspaceBackgroundDrawable : null); 3116 } 3117 3118 protected void changeWallpaperVisiblity(boolean visible) { 3119 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; 3120 int curflags = getWindow().getAttributes().flags 3121 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 3122 if (wpflags != curflags) { 3123 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); 3124 } 3125 setWorkspaceBackground(visible); 3126 } 3127 3128 private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { 3129 if (v instanceof LauncherTransitionable) { 3130 ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace); 3131 } 3132 } 3133 3134 private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 3135 if (v instanceof LauncherTransitionable) { 3136 ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace); 3137 } 3138 3139 // Update the workspace transition step as well 3140 dispatchOnLauncherTransitionStep(v, 0f); 3141 } 3142 3143 private void dispatchOnLauncherTransitionStep(View v, float t) { 3144 if (v instanceof LauncherTransitionable) { 3145 ((LauncherTransitionable) v).onLauncherTransitionStep(this, t); 3146 } 3147 } 3148 3149 private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 3150 if (v instanceof LauncherTransitionable) { 3151 ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace); 3152 } 3153 3154 // Update the workspace transition step as well 3155 dispatchOnLauncherTransitionStep(v, 1f); 3156 } 3157 3158 /** 3159 * Things to test when changing the following seven functions. 3160 * - Home from workspace 3161 * - from center screen 3162 * - from other screens 3163 * - Home from all apps 3164 * - from center screen 3165 * - from other screens 3166 * - Back from all apps 3167 * - from center screen 3168 * - from other screens 3169 * - Launch app from workspace and quit 3170 * - with back 3171 * - with home 3172 * - Launch app from all apps and quit 3173 * - with back 3174 * - with home 3175 * - Go to a screen that's not the default, then all 3176 * apps, and launch and app, and go back 3177 * - with back 3178 * -with home 3179 * - On workspace, long press power and go back 3180 * - with back 3181 * - with home 3182 * - On all apps, long press power and go back 3183 * - with back 3184 * - with home 3185 * - On workspace, power off 3186 * - On all apps, power off 3187 * - Launch an app and turn off the screen while in that app 3188 * - Go back with home key 3189 * - Go back with back key TODO: make this not go to workspace 3190 * - From all apps 3191 * - From workspace 3192 * - Enter and exit car mode (becuase it causes an extra configuration changed) 3193 * - From all apps 3194 * - From the center workspace 3195 * - From another workspace 3196 */ 3197 3198 /** 3199 * Zoom the camera out from the workspace to reveal 'toView'. 3200 * Assumes that the view to show is anchored at either the very top or very bottom 3201 * of the screen. 3202 */ 3203 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) { 3204 AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType(); 3205 showAppsCustomizeHelper(animated, springLoaded, contentType); 3206 } 3207 3208 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded, 3209 final AppsCustomizePagedView.ContentType contentType) { 3210 if (mStateAnimation != null) { 3211 mStateAnimation.setDuration(0); 3212 mStateAnimation.cancel(); 3213 mStateAnimation = null; 3214 } 3215 3216 boolean material = Utilities.isLmpOrAbove(); 3217 3218 final Resources res = getResources(); 3219 3220 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime); 3221 final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime); 3222 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime); 3223 final int itemsAlphaStagger = 3224 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger); 3225 3226 final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); 3227 final View fromView = mWorkspace; 3228 final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; 3229 3230 final ArrayList<View> layerViews = new ArrayList<View>(); 3231 3232 Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ? 3233 Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN; 3234 Animator workspaceAnim = 3235 mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews); 3236 if (!LauncherAppState.isDisableAllApps() 3237 || contentType == AppsCustomizePagedView.ContentType.Widgets) { 3238 // Set the content type for the all apps/widgets space 3239 mAppsCustomizeTabHost.setContentTypeImmediate(contentType); 3240 } 3241 3242 // If for some reason our views aren't initialized, don't animate 3243 boolean initialized = getAllAppsButton() != null; 3244 3245 if (animated && initialized) { 3246 mStateAnimation = LauncherAnimUtils.createAnimatorSet(); 3247 final AppsCustomizePagedView content = (AppsCustomizePagedView) 3248 toView.findViewById(R.id.apps_customize_pane_content); 3249 3250 final View page = content.getPageAt(content.getCurrentPage()); 3251 final View revealView = toView.findViewById(R.id.fake_page); 3252 3253 final float initialPanelAlpha = 1f; 3254 3255 final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets; 3256 if (isWidgetTray) { 3257 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); 3258 } else { 3259 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); 3260 } 3261 3262 // Hide the real page background, and swap in the fake one 3263 content.setPageBackgroundsVisible(false); 3264 revealView.setVisibility(View.VISIBLE); 3265 // We need to hide this view as the animation start will be posted. 3266 revealView.setAlpha(0); 3267 3268 int width = revealView.getMeasuredWidth(); 3269 int height = revealView.getMeasuredHeight(); 3270 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); 3271 3272 revealView.setTranslationY(0); 3273 revealView.setTranslationX(0); 3274 3275 // Get the y delta between the center of the page and the center of the all apps button 3276 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 3277 getAllAppsButton(), null); 3278 3279 float alpha = 0; 3280 float xDrift = 0; 3281 float yDrift = 0; 3282 if (material) { 3283 alpha = isWidgetTray ? 0.3f : 1f; 3284 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1]; 3285 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0]; 3286 } else { 3287 yDrift = 2 * height / 3; 3288 xDrift = 0; 3289 } 3290 final float initAlpha = alpha; 3291 3292 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 3293 layerViews.add(revealView); 3294 PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f); 3295 PropertyValuesHolder panelDriftY = 3296 PropertyValuesHolder.ofFloat("translationY", yDrift, 0); 3297 PropertyValuesHolder panelDriftX = 3298 PropertyValuesHolder.ofFloat("translationX", xDrift, 0); 3299 3300 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 3301 panelAlpha, panelDriftY, panelDriftX); 3302 3303 panelAlphaAndDrift.setDuration(revealDuration); 3304 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 3305 3306 mStateAnimation.play(panelAlphaAndDrift); 3307 3308 if (page != null) { 3309 page.setVisibility(View.VISIBLE); 3310 page.setLayerType(View.LAYER_TYPE_HARDWARE, null); 3311 layerViews.add(page); 3312 3313 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0); 3314 page.setTranslationY(yDrift); 3315 pageDrift.setDuration(revealDuration); 3316 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 3317 pageDrift.setStartDelay(itemsAlphaStagger); 3318 mStateAnimation.play(pageDrift); 3319 3320 page.setAlpha(0f); 3321 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f); 3322 itemsAlpha.setDuration(revealDuration); 3323 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 3324 itemsAlpha.setStartDelay(itemsAlphaStagger); 3325 mStateAnimation.play(itemsAlpha); 3326 } 3327 3328 View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator); 3329 pageIndicators.setAlpha(0.01f); 3330 ObjectAnimator indicatorsAlpha = 3331 ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f); 3332 indicatorsAlpha.setDuration(revealDuration); 3333 mStateAnimation.play(indicatorsAlpha); 3334 3335 if (material) { 3336 final View allApps = getAllAppsButton(); 3337 int allAppsButtonSize = LauncherAppState.getInstance(). 3338 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize; 3339 float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2; 3340 Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2, 3341 height / 2, startRadius, revealRadius); 3342 reveal.setDuration(revealDuration); 3343 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 3344 3345 reveal.addListener(new AnimatorListenerAdapter() { 3346 public void onAnimationStart(Animator animation) { 3347 if (!isWidgetTray) { 3348 allApps.setVisibility(View.INVISIBLE); 3349 } 3350 } 3351 public void onAnimationEnd(Animator animation) { 3352 if (!isWidgetTray) { 3353 allApps.setVisibility(View.VISIBLE); 3354 } 3355 } 3356 }); 3357 mStateAnimation.play(reveal); 3358 } 3359 3360 mStateAnimation.addListener(new AnimatorListenerAdapter() { 3361 @Override 3362 public void onAnimationEnd(Animator animation) { 3363 dispatchOnLauncherTransitionEnd(fromView, animated, false); 3364 dispatchOnLauncherTransitionEnd(toView, animated, false); 3365 3366 revealView.setVisibility(View.INVISIBLE); 3367 revealView.setLayerType(View.LAYER_TYPE_NONE, null); 3368 if (page != null) { 3369 page.setLayerType(View.LAYER_TYPE_NONE, null); 3370 } 3371 content.setPageBackgroundsVisible(true); 3372 3373 // Hide the search bar 3374 if (mSearchDropTargetBar != null) { 3375 mSearchDropTargetBar.hideSearchBar(false); 3376 } 3377 } 3378 3379 }); 3380 3381 if (workspaceAnim != null) { 3382 mStateAnimation.play(workspaceAnim); 3383 } 3384 3385 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 3386 dispatchOnLauncherTransitionPrepare(toView, animated, false); 3387 final AnimatorSet stateAnimation = mStateAnimation; 3388 final Runnable startAnimRunnable = new Runnable() { 3389 public void run() { 3390 // Check that mStateAnimation hasn't changed while 3391 // we waited for a layout/draw pass 3392 if (mStateAnimation != stateAnimation) 3393 return; 3394 dispatchOnLauncherTransitionStart(fromView, animated, false); 3395 dispatchOnLauncherTransitionStart(toView, animated, false); 3396 3397 revealView.setAlpha(initAlpha); 3398 if (Utilities.isLmpOrAbove()) { 3399 for (int i = 0; i < layerViews.size(); i++) { 3400 View v = layerViews.get(i); 3401 if (v != null) { 3402 boolean attached = true; 3403 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 3404 attached = v.isAttachedToWindow(); 3405 } 3406 if (attached) v.buildLayer(); 3407 } 3408 } 3409 } 3410 mStateAnimation.start(); 3411 } 3412 }; 3413 toView.bringToFront(); 3414 toView.setVisibility(View.VISIBLE); 3415 toView.post(startAnimRunnable); 3416 } else { 3417 toView.setTranslationX(0.0f); 3418 toView.setTranslationY(0.0f); 3419 toView.setScaleX(1.0f); 3420 toView.setScaleY(1.0f); 3421 toView.setVisibility(View.VISIBLE); 3422 toView.bringToFront(); 3423 3424 if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) { 3425 // Hide the search bar 3426 if (mSearchDropTargetBar != null) { 3427 mSearchDropTargetBar.hideSearchBar(false); 3428 } 3429 } 3430 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 3431 dispatchOnLauncherTransitionStart(fromView, animated, false); 3432 dispatchOnLauncherTransitionEnd(fromView, animated, false); 3433 dispatchOnLauncherTransitionPrepare(toView, animated, false); 3434 dispatchOnLauncherTransitionStart(toView, animated, false); 3435 dispatchOnLauncherTransitionEnd(toView, animated, false); 3436 } 3437 } 3438 3439 /** 3440 * Zoom the camera back into the workspace, hiding 'fromView'. 3441 * This is the opposite of showAppsCustomizeHelper. 3442 * @param animated If true, the transition will be animated. 3443 */ 3444 private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated, 3445 final boolean springLoaded, final Runnable onCompleteRunnable) { 3446 3447 if (mStateAnimation != null) { 3448 mStateAnimation.setDuration(0); 3449 mStateAnimation.cancel(); 3450 mStateAnimation = null; 3451 } 3452 3453 boolean material = Utilities.isLmpOrAbove(); 3454 Resources res = getResources(); 3455 3456 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime); 3457 final int fadeOutDuration = res.getInteger(R.integer.config_appsCustomizeFadeOutTime); 3458 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime); 3459 final int itemsAlphaStagger = 3460 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger); 3461 3462 final float scaleFactor = (float) 3463 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); 3464 final View fromView = mAppsCustomizeTabHost; 3465 final View toView = mWorkspace; 3466 Animator workspaceAnim = null; 3467 final ArrayList<View> layerViews = new ArrayList<View>(); 3468 3469 if (toState == Workspace.State.NORMAL) { 3470 workspaceAnim = mWorkspace.getChangeStateAnimation( 3471 toState, animated, layerViews); 3472 } else if (toState == Workspace.State.SPRING_LOADED || 3473 toState == Workspace.State.OVERVIEW) { 3474 workspaceAnim = mWorkspace.getChangeStateAnimation( 3475 toState, animated, layerViews); 3476 } 3477 3478 // If for some reason our views aren't initialized, don't animate 3479 boolean initialized = getAllAppsButton() != null; 3480 3481 if (animated && initialized) { 3482 mStateAnimation = LauncherAnimUtils.createAnimatorSet(); 3483 if (workspaceAnim != null) { 3484 mStateAnimation.play(workspaceAnim); 3485 } 3486 3487 final AppsCustomizePagedView content = (AppsCustomizePagedView) 3488 fromView.findViewById(R.id.apps_customize_pane_content); 3489 3490 final View page = content.getPageAt(content.getNextPage()); 3491 3492 // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases 3493 int count = content.getChildCount(); 3494 for (int i = 0; i < count; i++) { 3495 View child = content.getChildAt(i); 3496 if (child != page) { 3497 child.setVisibility(View.INVISIBLE); 3498 } 3499 } 3500 final View revealView = fromView.findViewById(R.id.fake_page); 3501 3502 // hideAppsCustomizeHelper is called in some cases when it is already hidden 3503 // don't perform all these no-op animations. In particularly, this was causing 3504 // the all-apps button to pop in and out. 3505 if (fromView.getVisibility() == View.VISIBLE) { 3506 AppsCustomizePagedView.ContentType contentType = content.getContentType(); 3507 final boolean isWidgetTray = 3508 contentType == AppsCustomizePagedView.ContentType.Widgets; 3509 3510 if (isWidgetTray) { 3511 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); 3512 } else { 3513 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); 3514 } 3515 3516 int width = revealView.getMeasuredWidth(); 3517 int height = revealView.getMeasuredHeight(); 3518 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); 3519 3520 // Hide the real page background, and swap in the fake one 3521 revealView.setVisibility(View.VISIBLE); 3522 content.setPageBackgroundsVisible(false); 3523 3524 final View allAppsButton = getAllAppsButton(); 3525 revealView.setTranslationY(0); 3526 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 3527 allAppsButton, null); 3528 3529 float xDrift = 0; 3530 float yDrift = 0; 3531 if (material) { 3532 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1]; 3533 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0]; 3534 } else { 3535 yDrift = 5 * height / 4; 3536 xDrift = 0; 3537 } 3538 3539 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 3540 TimeInterpolator decelerateInterpolator = material ? 3541 new LogDecelerateInterpolator(100, 0) : 3542 new LogDecelerateInterpolator(30, 0); 3543 3544 // The vertical motion of the apps panel should be delayed by one frame 3545 // from the conceal animation in order to give the right feel. We correpsondingly 3546 // shorten the duration so that the slide and conceal end at the same time. 3547 ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY", 3548 0, yDrift); 3549 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 3550 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 3551 panelDriftY.setInterpolator(decelerateInterpolator); 3552 mStateAnimation.play(panelDriftY); 3553 3554 ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX", 3555 0, xDrift); 3556 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 3557 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 3558 panelDriftX.setInterpolator(decelerateInterpolator); 3559 mStateAnimation.play(panelDriftX); 3560 3561 if (isWidgetTray || !material) { 3562 float finalAlpha = material ? 0.4f : 0f; 3563 revealView.setAlpha(1f); 3564 ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha", 3565 1f, finalAlpha); 3566 panelAlpha.setDuration(revealDuration); 3567 panelAlpha.setInterpolator(material ? decelerateInterpolator : 3568 new AccelerateInterpolator(1.5f)); 3569 mStateAnimation.play(panelAlpha); 3570 } 3571 3572 if (page != null) { 3573 page.setLayerType(View.LAYER_TYPE_HARDWARE, null); 3574 3575 ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY", 3576 0, yDrift); 3577 page.setTranslationY(0); 3578 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 3579 pageDrift.setInterpolator(decelerateInterpolator); 3580 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 3581 mStateAnimation.play(pageDrift); 3582 3583 page.setAlpha(1f); 3584 ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f); 3585 itemsAlpha.setDuration(100); 3586 itemsAlpha.setInterpolator(decelerateInterpolator); 3587 mStateAnimation.play(itemsAlpha); 3588 } 3589 3590 View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator); 3591 pageIndicators.setAlpha(1f); 3592 ObjectAnimator indicatorsAlpha = 3593 LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f); 3594 indicatorsAlpha.setDuration(revealDuration); 3595 indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f)); 3596 mStateAnimation.play(indicatorsAlpha); 3597 3598 width = revealView.getMeasuredWidth(); 3599 3600 if (material) { 3601 if (!isWidgetTray) { 3602 allAppsButton.setVisibility(View.INVISIBLE); 3603 } 3604 int allAppsButtonSize = LauncherAppState.getInstance(). 3605 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize; 3606 float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2; 3607 Animator reveal = 3608 LauncherAnimUtils.createCircularReveal(revealView, width / 2, 3609 height / 2, revealRadius, finalRadius); 3610 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 3611 reveal.setDuration(revealDuration); 3612 reveal.setStartDelay(itemsAlphaStagger); 3613 3614 reveal.addListener(new AnimatorListenerAdapter() { 3615 public void onAnimationEnd(Animator animation) { 3616 revealView.setVisibility(View.INVISIBLE); 3617 if (!isWidgetTray) { 3618 allAppsButton.setVisibility(View.VISIBLE); 3619 } 3620 } 3621 }); 3622 3623 mStateAnimation.play(reveal); 3624 } 3625 3626 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 3627 dispatchOnLauncherTransitionPrepare(toView, animated, true); 3628 mAppsCustomizeContent.stopScrolling(); 3629 } 3630 3631 mStateAnimation.addListener(new AnimatorListenerAdapter() { 3632 @Override 3633 public void onAnimationEnd(Animator animation) { 3634 fromView.setVisibility(View.GONE); 3635 dispatchOnLauncherTransitionEnd(fromView, animated, true); 3636 dispatchOnLauncherTransitionEnd(toView, animated, true); 3637 if (onCompleteRunnable != null) { 3638 onCompleteRunnable.run(); 3639 } 3640 3641 revealView.setLayerType(View.LAYER_TYPE_NONE, null); 3642 if (page != null) { 3643 page.setLayerType(View.LAYER_TYPE_NONE, null); 3644 } 3645 content.setPageBackgroundsVisible(true); 3646 // Unhide side pages 3647 int count = content.getChildCount(); 3648 for (int i = 0; i < count; i++) { 3649 View child = content.getChildAt(i); 3650 child.setVisibility(View.VISIBLE); 3651 } 3652 3653 // Reset page transforms 3654 if (page != null) { 3655 page.setTranslationX(0); 3656 page.setTranslationY(0); 3657 page.setAlpha(1); 3658 } 3659 content.setCurrentPage(content.getNextPage()); 3660 3661 mAppsCustomizeContent.updateCurrentPageScroll(); 3662 } 3663 }); 3664 3665 final AnimatorSet stateAnimation = mStateAnimation; 3666 final Runnable startAnimRunnable = new Runnable() { 3667 public void run() { 3668 // Check that mStateAnimation hasn't changed while 3669 // we waited for a layout/draw pass 3670 if (mStateAnimation != stateAnimation) 3671 return; 3672 dispatchOnLauncherTransitionStart(fromView, animated, false); 3673 dispatchOnLauncherTransitionStart(toView, animated, false); 3674 3675 if (Utilities.isLmpOrAbove()) { 3676 for (int i = 0; i < layerViews.size(); i++) { 3677 View v = layerViews.get(i); 3678 if (v != null) { 3679 boolean attached = true; 3680 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 3681 attached = v.isAttachedToWindow(); 3682 } 3683 if (attached) v.buildLayer(); 3684 } 3685 } 3686 } 3687 mStateAnimation.start(); 3688 } 3689 }; 3690 fromView.post(startAnimRunnable); 3691 } else { 3692 fromView.setVisibility(View.GONE); 3693 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 3694 dispatchOnLauncherTransitionStart(fromView, animated, true); 3695 dispatchOnLauncherTransitionEnd(fromView, animated, true); 3696 dispatchOnLauncherTransitionPrepare(toView, animated, true); 3697 dispatchOnLauncherTransitionStart(toView, animated, true); 3698 dispatchOnLauncherTransitionEnd(toView, animated, true); 3699 } 3700 } 3701 3702 @Override 3703 public void onTrimMemory(int level) { 3704 super.onTrimMemory(level); 3705 if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { 3706 mAppsCustomizeTabHost.onTrimMemory(); 3707 } 3708 } 3709 3710 protected void showWorkspace(boolean animated) { 3711 showWorkspace(animated, null); 3712 } 3713 3714 protected void showWorkspace() { 3715 showWorkspace(true); 3716 } 3717 3718 void showWorkspace(boolean animated, Runnable onCompleteRunnable) { 3719 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) { 3720 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE); 3721 mWorkspace.setVisibility(View.VISIBLE); 3722 hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable); 3723 3724 // Show the search bar (only animate if we were showing the drop target bar in spring 3725 // loaded mode) 3726 if (mSearchDropTargetBar != null) { 3727 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode); 3728 } 3729 3730 // Set focus to the AppsCustomize button 3731 if (mAllAppsButton != null) { 3732 mAllAppsButton.requestFocus(); 3733 } 3734 } 3735 3736 // Change the state *after* we've called all the transition code 3737 mState = State.WORKSPACE; 3738 3739 // Resume the auto-advance of widgets 3740 mUserPresent = true; 3741 updateRunning(); 3742 3743 // Send an accessibility event to announce the context change 3744 getWindow().getDecorView() 3745 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 3746 3747 onWorkspaceShown(animated); 3748 } 3749 3750 void showOverviewMode(boolean animated) { 3751 mWorkspace.setVisibility(View.VISIBLE); 3752 hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null); 3753 mState = State.WORKSPACE; 3754 onWorkspaceShown(animated); 3755 } 3756 3757 public void onWorkspaceShown(boolean animated) { 3758 } 3759 3760 void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType, 3761 boolean resetPageToZero) { 3762 if (mState != State.WORKSPACE) return; 3763 3764 if (resetPageToZero) { 3765 mAppsCustomizeTabHost.reset(); 3766 } 3767 showAppsCustomizeHelper(animated, false, contentType); 3768 mAppsCustomizeTabHost.post(new Runnable() { 3769 @Override 3770 public void run() { 3771 // We post this in-case the all apps view isn't yet constructed. 3772 mAppsCustomizeTabHost.requestFocus(); 3773 } 3774 }); 3775 3776 // Change the state *after* we've called all the transition code 3777 mState = State.APPS_CUSTOMIZE; 3778 3779 // Pause the auto-advance of widgets until we are out of AllApps 3780 mUserPresent = false; 3781 updateRunning(); 3782 closeFolder(); 3783 3784 // Send an accessibility event to announce the context change 3785 getWindow().getDecorView() 3786 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 3787 } 3788 3789 void enterSpringLoadedDragMode() { 3790 if (isAllAppsVisible()) { 3791 hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null); 3792 mState = State.APPS_CUSTOMIZE_SPRING_LOADED; 3793 } 3794 } 3795 3796 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, 3797 final Runnable onCompleteRunnable) { 3798 if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return; 3799 3800 mHandler.postDelayed(new Runnable() { 3801 @Override 3802 public void run() { 3803 if (successfulDrop) { 3804 // Before we show workspace, hide all apps again because 3805 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should 3806 // clean up our state transition functions 3807 mAppsCustomizeTabHost.setVisibility(View.GONE); 3808 showWorkspace(true, onCompleteRunnable); 3809 } else { 3810 exitSpringLoadedDragMode(); 3811 } 3812 } 3813 }, delay); 3814 } 3815 3816 void exitSpringLoadedDragMode() { 3817 if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) { 3818 final boolean animated = true; 3819 final boolean springLoaded = true; 3820 showAppsCustomizeHelper(animated, springLoaded); 3821 mState = State.APPS_CUSTOMIZE; 3822 } 3823 // Otherwise, we are not in spring loaded mode, so don't do anything. 3824 } 3825 3826 void lockAllApps() { 3827 // TODO 3828 } 3829 3830 void unlockAllApps() { 3831 // TODO 3832 } 3833 3834 /** 3835 * Hides the hotseat area. 3836 */ 3837 void hideHotseat(boolean animated) { 3838 if (!LauncherAppState.getInstance().isScreenLarge()) { 3839 if (animated) { 3840 if (mHotseat.getAlpha() != 0f) { 3841 int duration = 0; 3842 if (mSearchDropTargetBar != null) { 3843 duration = mSearchDropTargetBar.getTransitionOutDuration(); 3844 } 3845 mHotseat.animate().alpha(0f).setDuration(duration); 3846 } 3847 } else { 3848 mHotseat.setAlpha(0f); 3849 } 3850 } 3851 } 3852 3853 /** 3854 * Add an item from all apps or customize onto the given workspace screen. 3855 * If layout is null, add to the current screen. 3856 */ 3857 void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) { 3858 if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) { 3859 showOutOfSpaceMessage(isHotseatLayout(layout)); 3860 } 3861 } 3862 3863 /** Maps the current orientation to an index for referencing orientation correct global icons */ 3864 private int getCurrentOrientationIndexForGlobalIcons() { 3865 // default - 0, landscape - 1 3866 switch (getResources().getConfiguration().orientation) { 3867 case Configuration.ORIENTATION_LANDSCAPE: 3868 return 1; 3869 default: 3870 return 0; 3871 } 3872 } 3873 3874 private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) { 3875 try { 3876 PackageManager packageManager = getPackageManager(); 3877 // Look for the toolbar icon specified in the activity meta-data 3878 Bundle metaData = packageManager.getActivityInfo( 3879 activityName, PackageManager.GET_META_DATA).metaData; 3880 if (metaData != null) { 3881 int iconResId = metaData.getInt(resourceName); 3882 if (iconResId != 0) { 3883 Resources res = packageManager.getResourcesForActivity(activityName); 3884 return res.getDrawable(iconResId); 3885 } 3886 } 3887 } catch (NameNotFoundException e) { 3888 // This can happen if the activity defines an invalid drawable 3889 Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() + 3890 " not found", e); 3891 } catch (Resources.NotFoundException nfe) { 3892 // This can happen if the activity defines an invalid drawable 3893 Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(), 3894 nfe); 3895 } 3896 return null; 3897 } 3898 3899 // if successful in getting icon, return it; otherwise, set button to use default drawable 3900 private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity( 3901 int buttonId, ComponentName activityName, int fallbackDrawableId, 3902 String toolbarResourceName) { 3903 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); 3904 Resources r = getResources(); 3905 int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); 3906 int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); 3907 3908 TextView button = (TextView) findViewById(buttonId); 3909 // If we were unable to find the icon via the meta-data, use a generic one 3910 if (toolbarIcon == null) { 3911 toolbarIcon = r.getDrawable(fallbackDrawableId); 3912 toolbarIcon.setBounds(0, 0, w, h); 3913 if (button != null) { 3914 button.setCompoundDrawables(toolbarIcon, null, null, null); 3915 } 3916 return null; 3917 } else { 3918 toolbarIcon.setBounds(0, 0, w, h); 3919 if (button != null) { 3920 button.setCompoundDrawables(toolbarIcon, null, null, null); 3921 } 3922 return toolbarIcon.getConstantState(); 3923 } 3924 } 3925 3926 // if successful in getting icon, return it; otherwise, set button to use default drawable 3927 private Drawable.ConstantState updateButtonWithIconFromExternalActivity( 3928 int buttonId, ComponentName activityName, int fallbackDrawableId, 3929 String toolbarResourceName) { 3930 ImageView button = (ImageView) findViewById(buttonId); 3931 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); 3932 3933 if (button != null) { 3934 // If we were unable to find the icon via the meta-data, use a 3935 // generic one 3936 if (toolbarIcon == null) { 3937 button.setImageResource(fallbackDrawableId); 3938 } else { 3939 button.setImageDrawable(toolbarIcon); 3940 } 3941 } 3942 3943 return toolbarIcon != null ? toolbarIcon.getConstantState() : null; 3944 3945 } 3946 3947 private void updateTextButtonWithDrawable(int buttonId, Drawable d) { 3948 TextView button = (TextView) findViewById(buttonId); 3949 button.setCompoundDrawables(d, null, null, null); 3950 } 3951 3952 private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) { 3953 ImageView button = (ImageView) findViewById(buttonId); 3954 button.setImageDrawable(d.newDrawable(getResources())); 3955 } 3956 3957 private void invalidatePressedFocusedStates(View container, View button) { 3958 if (container instanceof HolographicLinearLayout) { 3959 HolographicLinearLayout layout = (HolographicLinearLayout) container; 3960 layout.invalidatePressedFocusedStates(); 3961 } else if (button instanceof HolographicImageView) { 3962 HolographicImageView view = (HolographicImageView) button; 3963 view.invalidatePressedFocusedStates(); 3964 } 3965 } 3966 3967 public View getQsbBar() { 3968 if (mQsb == null) { 3969 mQsb = mInflater.inflate(R.layout.qsb, mSearchDropTargetBar, false); 3970 mSearchDropTargetBar.addView(mQsb); 3971 } 3972 return mQsb; 3973 } 3974 3975 protected boolean updateGlobalSearchIcon() { 3976 final View searchButtonContainer = findViewById(R.id.search_button_container); 3977 final ImageView searchButton = (ImageView) findViewById(R.id.search_button); 3978 final View voiceButtonContainer = findViewById(R.id.voice_button_container); 3979 final View voiceButton = findViewById(R.id.voice_button); 3980 3981 final SearchManager searchManager = 3982 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 3983 ComponentName activityName = searchManager.getGlobalSearchActivity(); 3984 if (activityName != null) { 3985 int coi = getCurrentOrientationIndexForGlobalIcons(); 3986 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( 3987 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, 3988 TOOLBAR_SEARCH_ICON_METADATA_NAME); 3989 if (sGlobalSearchIcon[coi] == null) { 3990 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( 3991 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, 3992 TOOLBAR_ICON_METADATA_NAME); 3993 } 3994 3995 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE); 3996 searchButton.setVisibility(View.VISIBLE); 3997 invalidatePressedFocusedStates(searchButtonContainer, searchButton); 3998 return true; 3999 } else { 4000 // We disable both search and voice search when there is no global search provider 4001 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE); 4002 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); 4003 if (searchButton != null) searchButton.setVisibility(View.GONE); 4004 if (voiceButton != null) voiceButton.setVisibility(View.GONE); 4005 updateVoiceButtonProxyVisible(false); 4006 return false; 4007 } 4008 } 4009 4010 protected void updateGlobalSearchIcon(Drawable.ConstantState d) { 4011 final View searchButtonContainer = findViewById(R.id.search_button_container); 4012 final View searchButton = (ImageView) findViewById(R.id.search_button); 4013 updateButtonWithDrawable(R.id.search_button, d); 4014 invalidatePressedFocusedStates(searchButtonContainer, searchButton); 4015 } 4016 4017 protected boolean updateVoiceSearchIcon(boolean searchVisible) { 4018 final View voiceButtonContainer = findViewById(R.id.voice_button_container); 4019 final View voiceButton = findViewById(R.id.voice_button); 4020 4021 // We only show/update the voice search icon if the search icon is enabled as well 4022 final SearchManager searchManager = 4023 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 4024 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity(); 4025 4026 ComponentName activityName = null; 4027 if (globalSearchActivity != null) { 4028 // Check if the global search activity handles voice search 4029 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 4030 intent.setPackage(globalSearchActivity.getPackageName()); 4031 activityName = intent.resolveActivity(getPackageManager()); 4032 } 4033 4034 if (activityName == null) { 4035 // Fallback: check if an activity other than the global search activity 4036 // resolves this 4037 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); 4038 activityName = intent.resolveActivity(getPackageManager()); 4039 } 4040 if (searchVisible && activityName != null) { 4041 int coi = getCurrentOrientationIndexForGlobalIcons(); 4042 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( 4043 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, 4044 TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME); 4045 if (sVoiceSearchIcon[coi] == null) { 4046 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( 4047 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, 4048 TOOLBAR_ICON_METADATA_NAME); 4049 } 4050 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE); 4051 voiceButton.setVisibility(View.VISIBLE); 4052 updateVoiceButtonProxyVisible(false); 4053 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); 4054 return true; 4055 } else { 4056 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); 4057 if (voiceButton != null) voiceButton.setVisibility(View.GONE); 4058 updateVoiceButtonProxyVisible(false); 4059 return false; 4060 } 4061 } 4062 4063 protected void updateVoiceSearchIcon(Drawable.ConstantState d) { 4064 final View voiceButtonContainer = findViewById(R.id.voice_button_container); 4065 final View voiceButton = findViewById(R.id.voice_button); 4066 updateButtonWithDrawable(R.id.voice_button, d); 4067 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); 4068 } 4069 4070 public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) { 4071 final View voiceButtonProxy = findViewById(R.id.voice_button_proxy); 4072 if (voiceButtonProxy != null) { 4073 boolean visible = !forceDisableVoiceButtonProxy && 4074 mWorkspace.shouldVoiceButtonProxyBeVisible(); 4075 voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE); 4076 voiceButtonProxy.bringToFront(); 4077 } 4078 } 4079 4080 /** 4081 * This is an overrid eot disable the voice button proxy. If disabled is true, then the voice button proxy 4082 * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns. 4083 */ 4084 public void disableVoiceButtonProxy(boolean disabled) { 4085 updateVoiceButtonProxyVisible(disabled); 4086 } 4087 4088 @Override 4089 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 4090 final boolean result = super.dispatchPopulateAccessibilityEvent(event); 4091 final List<CharSequence> text = event.getText(); 4092 text.clear(); 4093 // Populate event with a fake title based on the current state. 4094 if (mState == State.APPS_CUSTOMIZE) { 4095 text.add(mAppsCustomizeTabHost.getContentTag()); 4096 } else { 4097 text.add(getString(R.string.all_apps_home_button_label)); 4098 } 4099 return result; 4100 } 4101 4102 /** 4103 * Receives notifications when system dialogs are to be closed. 4104 */ 4105 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver { 4106 @Override 4107 public void onReceive(Context context, Intent intent) { 4108 closeSystemDialogs(); 4109 } 4110 } 4111 4112 /** 4113 * Receives notifications whenever the appwidgets are reset. 4114 */ 4115 private class AppWidgetResetObserver extends ContentObserver { 4116 public AppWidgetResetObserver() { 4117 super(new Handler()); 4118 } 4119 4120 @Override 4121 public void onChange(boolean selfChange) { 4122 onAppWidgetReset(); 4123 } 4124 } 4125 4126 /** 4127 * If the activity is currently paused, signal that we need to run the passed Runnable 4128 * in onResume. 4129 * 4130 * This needs to be called from incoming places where resources might have been loaded 4131 * while we are paused. That is becaues the Configuration might be wrong 4132 * when we're not running, and if it comes back to what it was when we 4133 * were paused, we are not restarted. 4134 * 4135 * Implementation of the method from LauncherModel.Callbacks. 4136 * 4137 * @return true if we are currently paused. The caller might be able to 4138 * skip some work in that case since we will come back again. 4139 */ 4140 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) { 4141 if (mPaused) { 4142 Log.i(TAG, "Deferring update until onResume"); 4143 if (deletePreviousRunnables) { 4144 while (mBindOnResumeCallbacks.remove(run)) { 4145 } 4146 } 4147 mBindOnResumeCallbacks.add(run); 4148 return true; 4149 } else { 4150 return false; 4151 } 4152 } 4153 4154 private boolean waitUntilResume(Runnable run) { 4155 return waitUntilResume(run, false); 4156 } 4157 4158 public void addOnResumeCallback(Runnable run) { 4159 mOnResumeCallbacks.add(run); 4160 } 4161 4162 /** 4163 * If the activity is currently paused, signal that we need to re-run the loader 4164 * in onResume. 4165 * 4166 * This needs to be called from incoming places where resources might have been loaded 4167 * while we are paused. That is becaues the Configuration might be wrong 4168 * when we're not running, and if it comes back to what it was when we 4169 * were paused, we are not restarted. 4170 * 4171 * Implementation of the method from LauncherModel.Callbacks. 4172 * 4173 * @return true if we are currently paused. The caller might be able to 4174 * skip some work in that case since we will come back again. 4175 */ 4176 public boolean setLoadOnResume() { 4177 if (mPaused) { 4178 Log.i(TAG, "setLoadOnResume"); 4179 mOnResumeNeedsLoad = true; 4180 return true; 4181 } else { 4182 return false; 4183 } 4184 } 4185 4186 /** 4187 * Implementation of the method from LauncherModel.Callbacks. 4188 */ 4189 public int getCurrentWorkspaceScreen() { 4190 if (mWorkspace != null) { 4191 return mWorkspace.getCurrentPage(); 4192 } else { 4193 return SCREEN_COUNT / 2; 4194 } 4195 } 4196 4197 /** 4198 * Refreshes the shortcuts shown on the workspace. 4199 * 4200 * Implementation of the method from LauncherModel.Callbacks. 4201 */ 4202 public void startBinding() { 4203 setWorkspaceLoading(true); 4204 4205 // If we're starting binding all over again, clear any bind calls we'd postponed in 4206 // the past (see waitUntilResume) -- we don't need them since we're starting binding 4207 // from scratch again 4208 mBindOnResumeCallbacks.clear(); 4209 4210 // Clear the workspace because it's going to be rebound 4211 mWorkspace.clearDropTargets(); 4212 mWorkspace.removeAllWorkspaceScreens(); 4213 4214 mWidgetsToAdvance.clear(); 4215 if (mHotseat != null) { 4216 mHotseat.resetLayout(); 4217 } 4218 } 4219 4220 @Override 4221 public void bindScreens(ArrayList<Long> orderedScreenIds) { 4222 bindAddScreens(orderedScreenIds); 4223 4224 // If there are no screens, we need to have an empty screen 4225 if (orderedScreenIds.size() == 0) { 4226 mWorkspace.addExtraEmptyScreen(); 4227 } 4228 4229 // Create the custom content page (this call updates mDefaultScreen which calls 4230 // setCurrentPage() so ensure that all pages are added before calling this). 4231 if (hasCustomContentToLeft()) { 4232 mWorkspace.createCustomContentContainer(); 4233 populateCustomContentContainer(); 4234 } 4235 } 4236 4237 @Override 4238 public void bindAddScreens(ArrayList<Long> orderedScreenIds) { 4239 // Log to disk 4240 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true); 4241 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " + 4242 TextUtils.join(", ", orderedScreenIds), true); 4243 int count = orderedScreenIds.size(); 4244 for (int i = 0; i < count; i++) { 4245 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i)); 4246 } 4247 } 4248 4249 private boolean shouldShowWeightWatcher() { 4250 String spKey = LauncherAppState.getSharedPreferencesKey(); 4251 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); 4252 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT); 4253 4254 return show; 4255 } 4256 4257 private void toggleShowWeightWatcher() { 4258 String spKey = LauncherAppState.getSharedPreferencesKey(); 4259 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); 4260 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true); 4261 4262 show = !show; 4263 4264 SharedPreferences.Editor editor = sp.edit(); 4265 editor.putBoolean(SHOW_WEIGHT_WATCHER, show); 4266 editor.commit(); 4267 4268 if (mWeightWatcher != null) { 4269 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); 4270 } 4271 } 4272 4273 public void bindAppsAdded(final ArrayList<Long> newScreens, 4274 final ArrayList<ItemInfo> addNotAnimated, 4275 final ArrayList<ItemInfo> addAnimated, 4276 final ArrayList<AppInfo> addedApps) { 4277 Runnable r = new Runnable() { 4278 public void run() { 4279 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps); 4280 } 4281 }; 4282 if (waitUntilResume(r)) { 4283 return; 4284 } 4285 4286 // Add the new screens 4287 if (newScreens != null) { 4288 bindAddScreens(newScreens); 4289 } 4290 4291 // We add the items without animation on non-visible pages, and with 4292 // animations on the new page (which we will try and snap to). 4293 if (addNotAnimated != null && !addNotAnimated.isEmpty()) { 4294 bindItems(addNotAnimated, 0, 4295 addNotAnimated.size(), false); 4296 } 4297 if (addAnimated != null && !addAnimated.isEmpty()) { 4298 bindItems(addAnimated, 0, 4299 addAnimated.size(), true); 4300 } 4301 4302 // Remove the extra empty screen 4303 mWorkspace.removeExtraEmptyScreen(false, false); 4304 4305 if (!LauncherAppState.isDisableAllApps() && 4306 addedApps != null && mAppsCustomizeContent != null) { 4307 mAppsCustomizeContent.addApps(addedApps); 4308 } 4309 } 4310 4311 /** 4312 * Bind the items start-end from the list. 4313 * 4314 * Implementation of the method from LauncherModel.Callbacks. 4315 */ 4316 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end, 4317 final boolean forceAnimateIcons) { 4318 Runnable r = new Runnable() { 4319 public void run() { 4320 bindItems(shortcuts, start, end, forceAnimateIcons); 4321 } 4322 }; 4323 if (waitUntilResume(r)) { 4324 return; 4325 } 4326 4327 // Get the list of added shortcuts and intersect them with the set of shortcuts here 4328 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); 4329 final Collection<Animator> bounceAnims = new ArrayList<Animator>(); 4330 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation(); 4331 Workspace workspace = mWorkspace; 4332 long newShortcutsScreenId = -1; 4333 for (int i = start; i < end; i++) { 4334 final ItemInfo item = shortcuts.get(i); 4335 4336 // Short circuit if we are loading dock items for a configuration which has no dock 4337 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && 4338 mHotseat == null) { 4339 continue; 4340 } 4341 4342 switch (item.itemType) { 4343 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 4344 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 4345 ShortcutInfo info = (ShortcutInfo) item; 4346 View shortcut = createShortcut(info); 4347 4348 /* 4349 * TODO: FIX collision case 4350 */ 4351 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 4352 CellLayout cl = mWorkspace.getScreenWithId(item.screenId); 4353 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) { 4354 View v = cl.getChildAt(item.cellX, item.cellY); 4355 Object tag = v.getTag(); 4356 String desc = "Collision while binding workspace item: " + item 4357 + ". Collides with " + tag; 4358 if (LauncherAppState.isDogfoodBuild()) { 4359 throw (new RuntimeException(desc)); 4360 } else { 4361 Log.d(TAG, desc); 4362 } 4363 } 4364 } 4365 4366 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX, 4367 item.cellY, 1, 1); 4368 if (animateIcons) { 4369 // Animate all the applications up now 4370 shortcut.setAlpha(0f); 4371 shortcut.setScaleX(0f); 4372 shortcut.setScaleY(0f); 4373 bounceAnims.add(createNewAppBounceAnimation(shortcut, i)); 4374 newShortcutsScreenId = item.screenId; 4375 } 4376 break; 4377 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 4378 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, 4379 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), 4380 (FolderInfo) item, mIconCache); 4381 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX, 4382 item.cellY, 1, 1); 4383 break; 4384 default: 4385 throw new RuntimeException("Invalid Item Type"); 4386 } 4387 } 4388 4389 if (animateIcons) { 4390 // Animate to the correct page 4391 if (newShortcutsScreenId > -1) { 4392 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage()); 4393 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId); 4394 final Runnable startBounceAnimRunnable = new Runnable() { 4395 public void run() { 4396 anim.playTogether(bounceAnims); 4397 anim.start(); 4398 } 4399 }; 4400 if (newShortcutsScreenId != currentScreenId) { 4401 // We post the animation slightly delayed to prevent slowdowns 4402 // when we are loading right after we return to launcher. 4403 mWorkspace.postDelayed(new Runnable() { 4404 public void run() { 4405 if (mWorkspace != null) { 4406 mWorkspace.snapToPage(newScreenIndex); 4407 mWorkspace.postDelayed(startBounceAnimRunnable, 4408 NEW_APPS_ANIMATION_DELAY); 4409 } 4410 } 4411 }, NEW_APPS_PAGE_MOVE_DELAY); 4412 } else { 4413 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY); 4414 } 4415 } 4416 } 4417 workspace.requestLayout(); 4418 } 4419 4420 /** 4421 * Implementation of the method from LauncherModel.Callbacks. 4422 */ 4423 public void bindFolders(final HashMap<Long, FolderInfo> folders) { 4424 Runnable r = new Runnable() { 4425 public void run() { 4426 bindFolders(folders); 4427 } 4428 }; 4429 if (waitUntilResume(r)) { 4430 return; 4431 } 4432 sFolders.clear(); 4433 sFolders.putAll(folders); 4434 } 4435 4436 /** 4437 * Add the views for a widget to the workspace. 4438 * 4439 * Implementation of the method from LauncherModel.Callbacks. 4440 */ 4441 public void bindAppWidget(final LauncherAppWidgetInfo item) { 4442 Runnable r = new Runnable() { 4443 public void run() { 4444 bindAppWidget(item); 4445 } 4446 }; 4447 if (waitUntilResume(r)) { 4448 return; 4449 } 4450 4451 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; 4452 if (DEBUG_WIDGETS) { 4453 Log.d(TAG, "bindAppWidget: " + item); 4454 } 4455 final Workspace workspace = mWorkspace; 4456 4457 AppWidgetProviderInfo appWidgetInfo; 4458 if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) && 4459 ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) { 4460 4461 appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName); 4462 if (appWidgetInfo == null) { 4463 if (DEBUG_WIDGETS) { 4464 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId 4465 + " belongs to component " + item.providerName 4466 + ", as the povider is null"); 4467 } 4468 LauncherModel.deleteItemFromDatabase(this, item); 4469 return; 4470 } 4471 // Note: This assumes that the id remap broadcast is received before this step. 4472 // If that is not the case, the id remap will be ignored and user may see the 4473 // click to setup view. 4474 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null); 4475 pendingInfo.spanX = item.spanX; 4476 pendingInfo.spanY = item.spanY; 4477 pendingInfo.minSpanX = item.minSpanX; 4478 pendingInfo.minSpanY = item.minSpanY; 4479 Bundle options = 4480 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo); 4481 4482 int newWidgetId = mAppWidgetHost.allocateAppWidgetId(); 4483 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 4484 newWidgetId, appWidgetInfo, options); 4485 4486 // TODO consider showing a permission dialog when the widget is clicked. 4487 if (!success) { 4488 mAppWidgetHost.deleteAppWidgetId(newWidgetId); 4489 if (DEBUG_WIDGETS) { 4490 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId 4491 + " belongs to component " + item.providerName 4492 + ", as the launcher is unable to bing a new widget id"); 4493 } 4494 LauncherModel.deleteItemFromDatabase(this, item); 4495 return; 4496 } 4497 4498 item.appWidgetId = newWidgetId; 4499 4500 // If the widget has a configure activity, it is still needs to set it up, otherwise 4501 // the widget is ready to go. 4502 item.restoreStatus = (appWidgetInfo.configure == null) 4503 ? LauncherAppWidgetInfo.RESTORE_COMPLETED 4504 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 4505 4506 LauncherModel.updateItemInDatabase(this, item); 4507 } 4508 4509 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 4510 final int appWidgetId = item.appWidgetId; 4511 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); 4512 if (DEBUG_WIDGETS) { 4513 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); 4514 } 4515 4516 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 4517 } else { 4518 appWidgetInfo = null; 4519 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item); 4520 view.updateIcon(mIconCache); 4521 item.hostView = view; 4522 item.hostView.updateAppWidget(null); 4523 item.hostView.setOnClickListener(this); 4524 } 4525 4526 item.hostView.setTag(item); 4527 item.onBindAppWidget(this); 4528 4529 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX, 4530 item.cellY, item.spanX, item.spanY, false); 4531 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo); 4532 4533 workspace.requestLayout(); 4534 4535 if (DEBUG_WIDGETS) { 4536 Log.d(TAG, "bound widget id="+item.appWidgetId+" in " 4537 + (SystemClock.uptimeMillis()-start) + "ms"); 4538 } 4539 } 4540 4541 /** 4542 * Restores a pending widget. 4543 * 4544 * @param appWidgetId The app widget id 4545 * @param cellInfo The position on screen where to create the widget. 4546 */ 4547 private void completeRestoreAppWidget(final int appWidgetId) { 4548 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId); 4549 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) { 4550 Log.e(TAG, "Widget update called, when the widget no longer exists."); 4551 return; 4552 } 4553 4554 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); 4555 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; 4556 4557 mWorkspace.reinflateWidgetsIfNecessary(); 4558 LauncherModel.updateItemInDatabase(this, info); 4559 } 4560 4561 public void onPageBoundSynchronously(int page) { 4562 mSynchronouslyBoundPages.add(page); 4563 } 4564 4565 /** 4566 * Callback saying that there aren't any more items to bind. 4567 * 4568 * Implementation of the method from LauncherModel.Callbacks. 4569 */ 4570 public void finishBindingItems(final boolean upgradePath) { 4571 Runnable r = new Runnable() { 4572 public void run() { 4573 finishBindingItems(upgradePath); 4574 } 4575 }; 4576 if (waitUntilResume(r)) { 4577 return; 4578 } 4579 if (mSavedState != null) { 4580 if (!mWorkspace.hasFocus()) { 4581 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); 4582 } 4583 mSavedState = null; 4584 } 4585 4586 mWorkspace.restoreInstanceStateForRemainingPages(); 4587 4588 setWorkspaceLoading(false); 4589 sendLoadingCompleteBroadcastIfNecessary(); 4590 4591 // If we received the result of any pending adds while the loader was running (e.g. the 4592 // widget configuration forced an orientation change), process them now. 4593 if (sPendingAddItem != null) { 4594 final long screenId = completeAdd(sPendingAddItem); 4595 4596 // TODO: this moves the user to the page where the pending item was added. Ideally, 4597 // the screen would be guaranteed to exist after bind, and the page would be set through 4598 // the workspace restore process. 4599 mWorkspace.post(new Runnable() { 4600 @Override 4601 public void run() { 4602 mWorkspace.snapToScreenId(screenId); 4603 } 4604 }); 4605 sPendingAddItem = null; 4606 } 4607 4608 if (upgradePath) { 4609 mWorkspace.getUniqueComponents(true, null); 4610 mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null); 4611 } 4612 PackageInstallerCompat.getInstance(this).onFinishBind(); 4613 mModel.recheckRestoredItems(this); 4614 } 4615 4616 private void sendLoadingCompleteBroadcastIfNecessary() { 4617 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) { 4618 String permission = 4619 getResources().getString(R.string.receive_first_load_broadcast_permission); 4620 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE); 4621 sendBroadcast(intent, permission); 4622 SharedPreferences.Editor editor = mSharedPrefs.edit(); 4623 editor.putBoolean(FIRST_LOAD_COMPLETE, true); 4624 editor.apply(); 4625 } 4626 } 4627 4628 public boolean isAllAppsButtonRank(int rank) { 4629 if (mHotseat != null) { 4630 return mHotseat.isAllAppsButtonRank(rank); 4631 } 4632 return false; 4633 } 4634 4635 private boolean canRunNewAppsAnimation() { 4636 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); 4637 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); 4638 } 4639 4640 private ValueAnimator createNewAppBounceAnimation(View v, int i) { 4641 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v, 4642 PropertyValuesHolder.ofFloat("alpha", 1f), 4643 PropertyValuesHolder.ofFloat("scaleX", 1f), 4644 PropertyValuesHolder.ofFloat("scaleY", 1f)); 4645 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); 4646 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); 4647 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator()); 4648 return bounceAnim; 4649 } 4650 4651 public boolean useVerticalBarLayout() { 4652 return LauncherAppState.getInstance().getDynamicGrid(). 4653 getDeviceProfile().isVerticalBarLayout(); 4654 } 4655 4656 protected Rect getSearchBarBounds() { 4657 return LauncherAppState.getInstance().getDynamicGrid(). 4658 getDeviceProfile().getSearchBarBounds(); 4659 } 4660 4661 @Override 4662 public void bindSearchablesChanged() { 4663 boolean searchVisible = updateGlobalSearchIcon(); 4664 boolean voiceVisible = updateVoiceSearchIcon(searchVisible); 4665 if (mSearchDropTargetBar != null) { 4666 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); 4667 } 4668 } 4669 4670 /** 4671 * Add the icons for all apps. 4672 * 4673 * Implementation of the method from LauncherModel.Callbacks. 4674 */ 4675 public void bindAllApplications(final ArrayList<AppInfo> apps) { 4676 if (LauncherAppState.isDisableAllApps()) { 4677 if (mIntentsOnWorkspaceFromUpgradePath != null) { 4678 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) { 4679 getHotseat().addAllAppsFolder(mIconCache, apps, 4680 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace); 4681 } 4682 mIntentsOnWorkspaceFromUpgradePath = null; 4683 } 4684 if (mAppsCustomizeContent != null) { 4685 mAppsCustomizeContent.onPackagesUpdated( 4686 LauncherModel.getSortedWidgetsAndShortcuts(this)); 4687 } 4688 } else { 4689 if (mAppsCustomizeContent != null) { 4690 mAppsCustomizeContent.setApps(apps); 4691 mAppsCustomizeContent.onPackagesUpdated( 4692 LauncherModel.getSortedWidgetsAndShortcuts(this)); 4693 } 4694 } 4695 } 4696 4697 /** 4698 * A package was updated. 4699 * 4700 * Implementation of the method from LauncherModel.Callbacks. 4701 */ 4702 public void bindAppsUpdated(final ArrayList<AppInfo> apps) { 4703 Runnable r = new Runnable() { 4704 public void run() { 4705 bindAppsUpdated(apps); 4706 } 4707 }; 4708 if (waitUntilResume(r)) { 4709 return; 4710 } 4711 4712 if (mWorkspace != null) { 4713 mWorkspace.updateShortcutsAndWidgets(apps); 4714 } 4715 4716 if (!LauncherAppState.isDisableAllApps() && 4717 mAppsCustomizeContent != null) { 4718 mAppsCustomizeContent.updateApps(apps); 4719 } 4720 } 4721 4722 /** 4723 * Packages were restored 4724 */ 4725 public void bindAppsRestored(final ArrayList<AppInfo> apps) { 4726 Runnable r = new Runnable() { 4727 public void run() { 4728 bindAppsRestored(apps); 4729 } 4730 }; 4731 if (waitUntilResume(r)) { 4732 return; 4733 } 4734 4735 if (mWorkspace != null) { 4736 mWorkspace.updateShortcutsAndWidgets(apps); 4737 } 4738 } 4739 4740 /** 4741 * Update the state of a package, typically related to install state. 4742 * 4743 * Implementation of the method from LauncherModel.Callbacks. 4744 */ 4745 @Override 4746 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) { 4747 if (mWorkspace != null) { 4748 mWorkspace.updatePackageState(installInfo); 4749 } 4750 } 4751 4752 /** 4753 * Update the label and icon of all the icons in a package 4754 * 4755 * Implementation of the method from LauncherModel.Callbacks. 4756 */ 4757 @Override 4758 public void updatePackageBadge(String packageName) { 4759 if (mWorkspace != null) { 4760 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle()); 4761 } 4762 } 4763 4764 /** 4765 * A package was uninstalled. We take both the super set of packageNames 4766 * in addition to specific applications to remove, the reason being that 4767 * this can be called when a package is updated as well. In that scenario, 4768 * we only remove specific components from the workspace, where as 4769 * package-removal should clear all items by package name. 4770 * 4771 * Implementation of the method from LauncherModel.Callbacks. 4772 */ 4773 public void bindComponentsRemoved(final ArrayList<String> packageNames, 4774 final ArrayList<AppInfo> appInfos, final UserHandleCompat user) { 4775 Runnable r = new Runnable() { 4776 public void run() { 4777 bindComponentsRemoved(packageNames, appInfos, user); 4778 } 4779 }; 4780 if (waitUntilResume(r)) { 4781 return; 4782 } 4783 4784 if (!packageNames.isEmpty()) { 4785 mWorkspace.removeItemsByPackageName(packageNames, user); 4786 } 4787 if (!appInfos.isEmpty()) { 4788 mWorkspace.removeItemsByApplicationInfo(appInfos, user); 4789 } 4790 4791 // Notify the drag controller 4792 mDragController.onAppsRemoved(packageNames, appInfos); 4793 4794 // Update AllApps 4795 if (!LauncherAppState.isDisableAllApps() && 4796 mAppsCustomizeContent != null) { 4797 mAppsCustomizeContent.removeApps(appInfos); 4798 } 4799 } 4800 4801 /** 4802 * A number of packages were updated. 4803 */ 4804 private ArrayList<Object> mWidgetsAndShortcuts; 4805 private Runnable mBindPackagesUpdatedRunnable = new Runnable() { 4806 public void run() { 4807 bindPackagesUpdated(mWidgetsAndShortcuts); 4808 mWidgetsAndShortcuts = null; 4809 } 4810 }; 4811 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) { 4812 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) { 4813 mWidgetsAndShortcuts = widgetsAndShortcuts; 4814 return; 4815 } 4816 4817 // Update the widgets pane 4818 if (mAppsCustomizeContent != null) { 4819 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts); 4820 } 4821 } 4822 4823 private int mapConfigurationOriActivityInfoOri(int configOri) { 4824 final Display d = getWindowManager().getDefaultDisplay(); 4825 int naturalOri = Configuration.ORIENTATION_LANDSCAPE; 4826 switch (d.getRotation()) { 4827 case Surface.ROTATION_0: 4828 case Surface.ROTATION_180: 4829 // We are currently in the same basic orientation as the natural orientation 4830 naturalOri = configOri; 4831 break; 4832 case Surface.ROTATION_90: 4833 case Surface.ROTATION_270: 4834 // We are currently in the other basic orientation to the natural orientation 4835 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ? 4836 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; 4837 break; 4838 } 4839 4840 int[] oriMap = { 4841 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, 4842 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, 4843 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, 4844 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE 4845 }; 4846 // Since the map starts at portrait, we need to offset if this device's natural orientation 4847 // is landscape. 4848 int indexOffset = 0; 4849 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) { 4850 indexOffset = 1; 4851 } 4852 return oriMap[(d.getRotation() + indexOffset) % 4]; 4853 } 4854 4855 public boolean isRotationEnabled() { 4856 boolean enableRotation = sForceEnableRotation || 4857 getResources().getBoolean(R.bool.allow_rotation); 4858 return enableRotation; 4859 } 4860 public void lockScreenOrientation() { 4861 if (isRotationEnabled()) { 4862 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources() 4863 .getConfiguration().orientation)); 4864 } 4865 } 4866 public void unlockScreenOrientation(boolean immediate) { 4867 if (isRotationEnabled()) { 4868 if (immediate) { 4869 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 4870 } else { 4871 mHandler.postDelayed(new Runnable() { 4872 public void run() { 4873 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 4874 } 4875 }, mRestoreScreenOrientationDelay); 4876 } 4877 } 4878 } 4879 4880 /** 4881 * Called when the SearchBar hint should be changed. 4882 * 4883 * @param hint the hint to be displayed in the search bar. 4884 */ 4885 protected void onSearchBarHintChanged(String hint) { 4886 4887 } 4888 4889 protected boolean isLauncherPreinstalled() { 4890 PackageManager pm = getPackageManager(); 4891 try { 4892 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0); 4893 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 4894 return true; 4895 } else { 4896 return false; 4897 } 4898 } catch (NameNotFoundException e) { 4899 e.printStackTrace(); 4900 return false; 4901 } 4902 } 4903 4904 /** 4905 * This method indicates whether or not we should suggest default wallpaper dimensions 4906 * when our wallpaper cropper was not yet used to set a wallpaper. 4907 */ 4908 protected boolean overrideWallpaperDimensions() { 4909 return true; 4910 } 4911 4912 protected boolean shouldClingFocusHotseatApp() { 4913 return false; 4914 } 4915 protected String getFirstRunClingSearchBarHint() { 4916 return ""; 4917 } 4918 protected String getFirstRunCustomContentHint() { 4919 return ""; 4920 } 4921 protected int getFirstRunFocusedHotseatAppDrawableId() { 4922 return -1; 4923 } 4924 protected ComponentName getFirstRunFocusedHotseatAppComponentName() { 4925 return null; 4926 } 4927 protected int getFirstRunFocusedHotseatAppRank() { 4928 return -1; 4929 } 4930 protected String getFirstRunFocusedHotseatAppBubbleTitle() { 4931 return ""; 4932 } 4933 protected String getFirstRunFocusedHotseatAppBubbleDescription() { 4934 return ""; 4935 } 4936 4937 /** 4938 * To be overridden by subclasses to indicate that there is an activity to launch 4939 * before showing the standard launcher experience. 4940 */ 4941 protected boolean hasFirstRunActivity() { 4942 return false; 4943 } 4944 4945 /** 4946 * To be overridden by subclasses to launch any first run activity 4947 */ 4948 protected Intent getFirstRunActivity() { 4949 return null; 4950 } 4951 4952 private boolean shouldRunFirstRunActivity() { 4953 return !ActivityManager.isRunningInTestHarness() && 4954 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); 4955 } 4956 4957 protected boolean hasRunFirstRunActivity() { 4958 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); 4959 } 4960 4961 public boolean showFirstRunActivity() { 4962 if (shouldRunFirstRunActivity() && 4963 hasFirstRunActivity()) { 4964 Intent firstRunIntent = getFirstRunActivity(); 4965 if (firstRunIntent != null) { 4966 startActivity(firstRunIntent); 4967 markFirstRunActivityShown(); 4968 return true; 4969 } 4970 } 4971 return false; 4972 } 4973 4974 private void markFirstRunActivityShown() { 4975 SharedPreferences.Editor editor = mSharedPrefs.edit(); 4976 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true); 4977 editor.apply(); 4978 } 4979 4980 /** 4981 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro 4982 * screen that must be displayed and dismissed. 4983 */ 4984 protected boolean hasDismissableIntroScreen() { 4985 return false; 4986 } 4987 4988 /** 4989 * Full screen intro screen to be shown and dismissed before the launcher can be used. 4990 */ 4991 protected View getIntroScreen() { 4992 return null; 4993 } 4994 4995 /** 4996 * To be overriden by subclasses to indicate whether the in-activity intro screen has been 4997 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false. 4998 */ 4999 private boolean shouldShowIntroScreen() { 5000 return hasDismissableIntroScreen() && 5001 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false); 5002 } 5003 5004 protected void showIntroScreen() { 5005 View introScreen = getIntroScreen(); 5006 changeWallpaperVisiblity(false); 5007 if (introScreen != null) { 5008 mDragLayer.showOverlayView(introScreen); 5009 } 5010 } 5011 5012 public void dismissIntroScreen() { 5013 markIntroScreenDismissed(); 5014 if (showFirstRunActivity()) { 5015 // We delay hiding the intro view until the first run activity is showing. This 5016 // avoids a blip. 5017 mWorkspace.postDelayed(new Runnable() { 5018 @Override 5019 public void run() { 5020 mDragLayer.dismissOverlayView(); 5021 showFirstRunClings(); 5022 } 5023 }, ACTIVITY_START_DELAY); 5024 } else { 5025 mDragLayer.dismissOverlayView(); 5026 showFirstRunClings(); 5027 } 5028 changeWallpaperVisiblity(true); 5029 } 5030 5031 private void markIntroScreenDismissed() { 5032 SharedPreferences.Editor editor = mSharedPrefs.edit(); 5033 editor.putBoolean(INTRO_SCREEN_DISMISSED, true); 5034 editor.apply(); 5035 } 5036 5037 private void showFirstRunClings() { 5038 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled 5039 // on the device, then we always show the first run cling experience (or if there is no 5040 // launcher2). Otherwise, we prompt the user upon started for migration 5041 LauncherClings launcherClings = new LauncherClings(this); 5042 if (launcherClings.shouldShowFirstRunOrMigrationClings()) { 5043 if (mModel.canMigrateFromOldLauncherDb(this)) { 5044 launcherClings.showMigrationCling(); 5045 } else { 5046 launcherClings.showLongPressCling(true); 5047 } 5048 } 5049 } 5050 5051 void showWorkspaceSearchAndHotseat() { 5052 if (mWorkspace != null) mWorkspace.setAlpha(1f); 5053 if (mHotseat != null) mHotseat.setAlpha(1f); 5054 if (mPageIndicators != null) mPageIndicators.setAlpha(1f); 5055 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false); 5056 } 5057 5058 void hideWorkspaceSearchAndHotseat() { 5059 if (mWorkspace != null) mWorkspace.setAlpha(0f); 5060 if (mHotseat != null) mHotseat.setAlpha(0f); 5061 if (mPageIndicators != null) mPageIndicators.setAlpha(0f); 5062 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false); 5063 } 5064 5065 public ItemInfo createAppDragInfo(Intent appLaunchIntent) { 5066 // Called from search suggestion, not supported in other profiles. 5067 final UserHandleCompat myUser = UserHandleCompat.myUserHandle(); 5068 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); 5069 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent, 5070 myUser); 5071 if (activityInfo == null) { 5072 return null; 5073 } 5074 return new AppInfo(this, activityInfo, myUser, mIconCache, null); 5075 } 5076 5077 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption, 5078 Bitmap icon) { 5079 // Called from search suggestion, not supported in other profiles. 5080 return createShortcutDragInfo(shortcutIntent, caption, icon, 5081 UserHandleCompat.myUserHandle()); 5082 } 5083 5084 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption, 5085 Bitmap icon, UserHandleCompat user) { 5086 UserManagerCompat userManager = UserManagerCompat.getInstance(this); 5087 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user); 5088 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user); 5089 } 5090 5091 protected void moveWorkspaceToDefaultScreen() { 5092 mWorkspace.moveToDefaultScreen(false); 5093 } 5094 5095 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) { 5096 dragView.setTag(dragInfo); 5097 mWorkspace.onExternalDragStartedWithItem(dragView); 5098 mWorkspace.beginExternalDragShared(dragView, source); 5099 } 5100 5101 @Override 5102 public void onPageSwitch(View newPage, int newPageIndex) { 5103 } 5104 5105 /** 5106 * Prints out out state for debugging. 5107 */ 5108 public void dumpState() { 5109 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this); 5110 Log.d(TAG, "mSavedState=" + mSavedState); 5111 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); 5112 Log.d(TAG, "mRestoring=" + mRestoring); 5113 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); 5114 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); 5115 Log.d(TAG, "sFolders.size=" + sFolders.size()); 5116 mModel.dumpState(); 5117 5118 if (mAppsCustomizeContent != null) { 5119 mAppsCustomizeContent.dumpState(); 5120 } 5121 Log.d(TAG, "END launcher3 dump state"); 5122 } 5123 5124 @Override 5125 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 5126 super.dump(prefix, fd, writer, args); 5127 synchronized (sDumpLogs) { 5128 writer.println(" "); 5129 writer.println("Debug logs: "); 5130 for (int i = 0; i < sDumpLogs.size(); i++) { 5131 writer.println(" " + sDumpLogs.get(i)); 5132 } 5133 } 5134 } 5135 5136 public static void dumpDebugLogsToConsole() { 5137 if (DEBUG_DUMP_LOG) { 5138 synchronized (sDumpLogs) { 5139 Log.d(TAG, ""); 5140 Log.d(TAG, "*********************"); 5141 Log.d(TAG, "Launcher debug logs: "); 5142 for (int i = 0; i < sDumpLogs.size(); i++) { 5143 Log.d(TAG, " " + sDumpLogs.get(i)); 5144 } 5145 Log.d(TAG, "*********************"); 5146 Log.d(TAG, ""); 5147 } 5148 } 5149 } 5150 5151 public static void addDumpLog(String tag, String log, boolean debugLog) { 5152 addDumpLog(tag, log, null, debugLog); 5153 } 5154 5155 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) { 5156 if (debugLog) { 5157 if (e != null) { 5158 Log.d(tag, log, e); 5159 } else { 5160 Log.d(tag, log); 5161 } 5162 } 5163 if (DEBUG_DUMP_LOG) { 5164 sDateStamp.setTime(System.currentTimeMillis()); 5165 synchronized (sDumpLogs) { 5166 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log 5167 + (e == null ? "" : (", Exception: " + e))); 5168 } 5169 } 5170 } 5171 5172 public void dumpLogsToLocalData() { 5173 if (DEBUG_DUMP_LOG) { 5174 new AsyncTask<Void, Void, Void>() { 5175 public Void doInBackground(Void ... args) { 5176 boolean success = false; 5177 sDateStamp.setTime(sRunStart); 5178 String FILENAME = sDateStamp.getMonth() + "-" 5179 + sDateStamp.getDay() + "_" 5180 + sDateStamp.getHours() + "-" 5181 + sDateStamp.getMinutes() + "_" 5182 + sDateStamp.getSeconds() + ".txt"; 5183 5184 FileOutputStream fos = null; 5185 File outFile = null; 5186 try { 5187 outFile = new File(getFilesDir(), FILENAME); 5188 outFile.createNewFile(); 5189 fos = new FileOutputStream(outFile); 5190 } catch (Exception e) { 5191 e.printStackTrace(); 5192 } 5193 if (fos != null) { 5194 PrintWriter writer = new PrintWriter(fos); 5195 5196 writer.println(" "); 5197 writer.println("Debug logs: "); 5198 synchronized (sDumpLogs) { 5199 for (int i = 0; i < sDumpLogs.size(); i++) { 5200 writer.println(" " + sDumpLogs.get(i)); 5201 } 5202 } 5203 writer.close(); 5204 } 5205 try { 5206 if (fos != null) { 5207 fos.close(); 5208 success = true; 5209 } 5210 } catch (IOException e) { 5211 e.printStackTrace(); 5212 } 5213 return null; 5214 } 5215 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); 5216 } 5217 } 5218 } 5219 5220 interface LauncherTransitionable { 5221 View getContent(); 5222 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace); 5223 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace); 5224 void onLauncherTransitionStep(Launcher l, float t); 5225 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace); 5226 } 5227 5228 interface DebugIntents { 5229 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE"; 5230 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE"; 5231 } 5232