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