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