Home | History | Annotate | Download | only in launcher2
      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.animation.Animator;
     21 import android.animation.AnimatorListenerAdapter;
     22 import android.animation.AnimatorSet;
     23 import android.animation.ObjectAnimator;
     24 import android.animation.PropertyValuesHolder;
     25 import android.animation.ValueAnimator;
     26 import android.app.Activity;
     27 import android.app.ActivityManager;
     28 import android.app.AlertDialog;
     29 import android.app.Dialog;
     30 import android.app.SearchManager;
     31 import android.app.StatusBarManager;
     32 import android.appwidget.AppWidgetHostView;
     33 import android.appwidget.AppWidgetManager;
     34 import android.appwidget.AppWidgetProviderInfo;
     35 import android.content.ActivityNotFoundException;
     36 import android.content.BroadcastReceiver;
     37 import android.content.ClipData;
     38 import android.content.ClipDescription;
     39 import android.content.ComponentCallbacks2;
     40 import android.content.ComponentName;
     41 import android.content.ContentResolver;
     42 import android.content.Context;
     43 import android.content.DialogInterface;
     44 import android.content.Intent;
     45 import android.content.IntentFilter;
     46 import android.content.SharedPreferences;
     47 import android.content.pm.ActivityInfo;
     48 import android.content.pm.PackageManager;
     49 import android.content.pm.PackageManager.NameNotFoundException;
     50 import android.content.res.Configuration;
     51 import android.content.res.Resources;
     52 import android.database.ContentObserver;
     53 import android.graphics.Rect;
     54 import android.graphics.drawable.Drawable;
     55 import android.net.Uri;
     56 import android.os.AsyncTask;
     57 import android.os.Build;
     58 import android.os.Bundle;
     59 import android.os.Environment;
     60 import android.os.Handler;
     61 import android.os.Message;
     62 import android.os.SystemClock;
     63 import android.os.SystemProperties;
     64 import android.provider.Settings;
     65 import android.speech.RecognizerIntent;
     66 import android.text.Selection;
     67 import android.text.SpannableStringBuilder;
     68 import android.text.TextUtils;
     69 import android.text.method.TextKeyListener;
     70 import android.util.Log;
     71 import android.view.Display;
     72 import android.view.HapticFeedbackConstants;
     73 import android.view.KeyEvent;
     74 import android.view.LayoutInflater;
     75 import android.view.Menu;
     76 import android.view.MenuItem;
     77 import android.view.MotionEvent;
     78 import android.view.Surface;
     79 import android.view.View;
     80 import android.view.View.OnLongClickListener;
     81 import android.view.ViewGroup;
     82 import android.view.ViewTreeObserver;
     83 import android.view.WindowManager;
     84 import android.view.accessibility.AccessibilityEvent;
     85 import android.view.animation.AccelerateDecelerateInterpolator;
     86 import android.view.animation.AccelerateInterpolator;
     87 import android.view.animation.DecelerateInterpolator;
     88 import android.view.inputmethod.InputMethodManager;
     89 import android.widget.Advanceable;
     90 import android.widget.EditText;
     91 import android.widget.ImageView;
     92 import android.widget.TextView;
     93 import android.widget.Toast;
     94 
     95 import com.android.common.Search;
     96 import com.android.launcher.R;
     97 import com.android.launcher2.DropTarget.DragObject;
     98 
     99 import java.io.DataInputStream;
    100 import java.io.DataOutputStream;
    101 import java.io.FileDescriptor;
    102 import java.io.FileNotFoundException;
    103 import java.io.IOException;
    104 import java.io.PrintWriter;
    105 import java.util.ArrayList;
    106 import java.util.HashMap;
    107 
    108 /**
    109  * Default launcher application.
    110  */
    111 public final class Launcher extends Activity
    112         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
    113                    AllAppsView.Watcher, View.OnTouchListener {
    114     static final String TAG = "Launcher";
    115     static final boolean LOGD = false;
    116 
    117     static final boolean PROFILE_STARTUP = false;
    118     static final boolean DEBUG_WIDGETS = false;
    119 
    120     private static final int MENU_GROUP_WALLPAPER = 1;
    121     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
    122     private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1;
    123     private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1;
    124     private static final int MENU_HELP = MENU_SYSTEM_SETTINGS + 1;
    125 
    126     private static final int REQUEST_CREATE_SHORTCUT = 1;
    127     private static final int REQUEST_CREATE_APPWIDGET = 5;
    128     private static final int REQUEST_PICK_APPLICATION = 6;
    129     private static final int REQUEST_PICK_SHORTCUT = 7;
    130     private static final int REQUEST_PICK_APPWIDGET = 9;
    131     private static final int REQUEST_PICK_WALLPAPER = 10;
    132 
    133     static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
    134 
    135     static final int SCREEN_COUNT = 5;
    136     static final int DEFAULT_SCREEN = 2;
    137 
    138     static final int DIALOG_CREATE_SHORTCUT = 1;
    139     static final int DIALOG_RENAME_FOLDER = 2;
    140 
    141     private static final String PREFERENCES = "launcher.preferences";
    142 
    143     // Type: int
    144     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
    145     // Type: int
    146     private static final String RUNTIME_STATE = "launcher.state";
    147     // Type: int
    148     private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
    149     // Type: int
    150     private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
    151     // Type: int
    152     private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
    153     // Type: int
    154     private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
    155     // Type: boolean
    156     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
    157     // Type: long
    158     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
    159 
    160     private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
    161 
    162     /** The different states that Launcher can be in. */
    163     private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
    164     private State mState = State.WORKSPACE;
    165     private AnimatorSet mStateAnimation;
    166     private AnimatorSet mDividerAnimator;
    167 
    168     static final int APPWIDGET_HOST_ID = 1024;
    169     private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
    170     private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
    171     private static final int SHOW_CLING_DURATION = 550;
    172     private static final int DISMISS_CLING_DURATION = 250;
    173 
    174     private static final Object sLock = new Object();
    175     private static int sScreen = DEFAULT_SCREEN;
    176 
    177     private final BroadcastReceiver mCloseSystemDialogsReceiver
    178             = new CloseSystemDialogsIntentReceiver();
    179     private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
    180 
    181     private LayoutInflater mInflater;
    182 
    183     private Workspace mWorkspace;
    184     private View mQsbDivider;
    185     private View mDockDivider;
    186     private DragLayer mDragLayer;
    187     private DragController mDragController;
    188 
    189     private AppWidgetManager mAppWidgetManager;
    190     private LauncherAppWidgetHost mAppWidgetHost;
    191 
    192     private ItemInfo mPendingAddInfo = new ItemInfo();
    193     private int[] mTmpAddItemCellCoordinates = new int[2];
    194 
    195     private FolderInfo mFolderInfo;
    196 
    197     private Hotseat mHotseat;
    198     private View mAllAppsButton;
    199 
    200     private SearchDropTargetBar mSearchDropTargetBar;
    201     private AppsCustomizeTabHost mAppsCustomizeTabHost;
    202     private AppsCustomizePagedView mAppsCustomizeContent;
    203     private boolean mAutoAdvanceRunning = false;
    204 
    205     private Bundle mSavedState;
    206 
    207     private SpannableStringBuilder mDefaultKeySsb = null;
    208 
    209     private boolean mWorkspaceLoading = true;
    210 
    211     private boolean mPaused = true;
    212     private boolean mRestoring;
    213     private boolean mWaitingForResult;
    214     private boolean mOnResumeNeedsLoad;
    215 
    216     private Bundle mSavedInstanceState;
    217 
    218     private LauncherModel mModel;
    219     private IconCache mIconCache;
    220     private boolean mUserPresent = true;
    221     private boolean mVisible = false;
    222     private boolean mAttached = false;
    223 
    224     private static LocaleConfiguration sLocaleConfiguration = null;
    225 
    226     private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
    227 
    228     private Intent mAppMarketIntent = null;
    229 
    230     // Related to the auto-advancing of widgets
    231     private final int ADVANCE_MSG = 1;
    232     private final int mAdvanceInterval = 20000;
    233     private final int mAdvanceStagger = 250;
    234     private long mAutoAdvanceSentTime;
    235     private long mAutoAdvanceTimeLeft = -1;
    236     private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
    237         new HashMap<View, AppWidgetProviderInfo>();
    238 
    239     // Determines how long to wait after a rotation before restoring the screen orientation to
    240     // match the sensor state.
    241     private final int mRestoreScreenOrientationDelay = 500;
    242 
    243     // External icons saved in case of resource changes, orientation, etc.
    244     private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
    245     private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
    246     private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
    247 
    248     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
    249 
    250 
    251     private BubbleTextView mWaitingForResume;
    252 
    253     private Runnable mBuildLayersRunnable = new Runnable() {
    254         public void run() {
    255             if (mWorkspace != null) {
    256                 mWorkspace.buildPageHardwareLayers();
    257             }
    258         }
    259     };
    260 
    261     private static ArrayList<PendingAddArguments> sPendingAddList
    262             = new ArrayList<PendingAddArguments>();
    263 
    264     private static class PendingAddArguments {
    265         int requestCode;
    266         Intent intent;
    267         long container;
    268         int screen;
    269         int cellX;
    270         int cellY;
    271     }
    272 
    273     @Override
    274     protected void onCreate(Bundle savedInstanceState) {
    275         super.onCreate(savedInstanceState);
    276         LauncherApplication app = ((LauncherApplication)getApplication());
    277         mModel = app.setLauncher(this);
    278         mIconCache = app.getIconCache();
    279         mDragController = new DragController(this);
    280         mInflater = getLayoutInflater();
    281 
    282         mAppWidgetManager = AppWidgetManager.getInstance(this);
    283         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
    284         mAppWidgetHost.startListening();
    285 
    286         if (PROFILE_STARTUP) {
    287             android.os.Debug.startMethodTracing(
    288                     Environment.getExternalStorageDirectory() + "/launcher");
    289         }
    290 
    291         checkForLocaleChange();
    292         setContentView(R.layout.launcher);
    293         setupViews();
    294         showFirstRunWorkspaceCling();
    295 
    296         registerContentObservers();
    297 
    298         lockAllApps();
    299 
    300         mSavedState = savedInstanceState;
    301         restoreState(mSavedState);
    302 
    303         // Update customization drawer _after_ restoring the states
    304         if (mAppsCustomizeContent != null) {
    305             mAppsCustomizeContent.onPackagesUpdated();
    306         }
    307 
    308         if (PROFILE_STARTUP) {
    309             android.os.Debug.stopMethodTracing();
    310         }
    311 
    312         if (!mRestoring) {
    313             mModel.startLoader(this, true);
    314         }
    315 
    316         if (!mModel.isAllAppsLoaded()) {
    317             ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
    318             mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
    319         }
    320 
    321         // For handling default keys
    322         mDefaultKeySsb = new SpannableStringBuilder();
    323         Selection.setSelection(mDefaultKeySsb, 0);
    324 
    325         IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    326         registerReceiver(mCloseSystemDialogsReceiver, filter);
    327 
    328         boolean searchVisible = false;
    329         boolean voiceVisible = false;
    330         // If we have a saved version of these external icons, we load them up immediately
    331         int coi = getCurrentOrientationIndexForGlobalIcons();
    332         if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
    333                 sAppMarketIcon[coi] == null) {
    334             updateAppMarketIcon();
    335             searchVisible = updateGlobalSearchIcon();
    336             voiceVisible = updateVoiceSearchIcon(searchVisible);
    337         }
    338         if (sGlobalSearchIcon[coi] != null) {
    339              updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
    340              searchVisible = true;
    341         }
    342         if (sVoiceSearchIcon[coi] != null) {
    343             updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
    344             voiceVisible = true;
    345         }
    346         if (sAppMarketIcon[coi] != null) {
    347             updateAppMarketIcon(sAppMarketIcon[coi]);
    348         }
    349         mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
    350 
    351         // On large interfaces, we want the screen to auto-rotate based on the current orientation
    352         if (LauncherApplication.isScreenLarge() || Build.TYPE.contentEquals("eng")) {
    353             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
    354         }
    355     }
    356 
    357     private void checkForLocaleChange() {
    358         if (sLocaleConfiguration == null) {
    359             new AsyncTask<Void, Void, LocaleConfiguration>() {
    360                 @Override
    361                 protected LocaleConfiguration doInBackground(Void... unused) {
    362                     LocaleConfiguration localeConfiguration = new LocaleConfiguration();
    363                     readConfiguration(Launcher.this, localeConfiguration);
    364                     return localeConfiguration;
    365                 }
    366 
    367                 @Override
    368                 protected void onPostExecute(LocaleConfiguration result) {
    369                     sLocaleConfiguration = result;
    370                     checkForLocaleChange();  // recursive, but now with a locale configuration
    371                 }
    372             }.execute();
    373             return;
    374         }
    375 
    376         final Configuration configuration = getResources().getConfiguration();
    377 
    378         final String previousLocale = sLocaleConfiguration.locale;
    379         final String locale = configuration.locale.toString();
    380 
    381         final int previousMcc = sLocaleConfiguration.mcc;
    382         final int mcc = configuration.mcc;
    383 
    384         final int previousMnc = sLocaleConfiguration.mnc;
    385         final int mnc = configuration.mnc;
    386 
    387         boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
    388 
    389         if (localeChanged) {
    390             sLocaleConfiguration.locale = locale;
    391             sLocaleConfiguration.mcc = mcc;
    392             sLocaleConfiguration.mnc = mnc;
    393 
    394             mIconCache.flush();
    395 
    396             final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
    397             new Thread("WriteLocaleConfiguration") {
    398                 @Override
    399                 public void run() {
    400                     writeConfiguration(Launcher.this, localeConfiguration);
    401                 }
    402             }.start();
    403         }
    404     }
    405 
    406     private static class LocaleConfiguration {
    407         public String locale;
    408         public int mcc = -1;
    409         public int mnc = -1;
    410     }
    411 
    412     private static void readConfiguration(Context context, LocaleConfiguration configuration) {
    413         DataInputStream in = null;
    414         try {
    415             in = new DataInputStream(context.openFileInput(PREFERENCES));
    416             configuration.locale = in.readUTF();
    417             configuration.mcc = in.readInt();
    418             configuration.mnc = in.readInt();
    419         } catch (FileNotFoundException e) {
    420             // Ignore
    421         } catch (IOException e) {
    422             // Ignore
    423         } finally {
    424             if (in != null) {
    425                 try {
    426                     in.close();
    427                 } catch (IOException e) {
    428                     // Ignore
    429                 }
    430             }
    431         }
    432     }
    433 
    434     private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
    435         DataOutputStream out = null;
    436         try {
    437             out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
    438             out.writeUTF(configuration.locale);
    439             out.writeInt(configuration.mcc);
    440             out.writeInt(configuration.mnc);
    441             out.flush();
    442         } catch (FileNotFoundException e) {
    443             // Ignore
    444         } catch (IOException e) {
    445             //noinspection ResultOfMethodCallIgnored
    446             context.getFileStreamPath(PREFERENCES).delete();
    447         } finally {
    448             if (out != null) {
    449                 try {
    450                     out.close();
    451                 } catch (IOException e) {
    452                     // Ignore
    453                 }
    454             }
    455         }
    456     }
    457 
    458     public DragLayer getDragLayer() {
    459         return mDragLayer;
    460     }
    461 
    462     static int getScreen() {
    463         synchronized (sLock) {
    464             return sScreen;
    465         }
    466     }
    467 
    468     static void setScreen(int screen) {
    469         synchronized (sLock) {
    470             sScreen = screen;
    471         }
    472     }
    473 
    474     /**
    475      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
    476      * a configuration step, this allows the proper animations to run after other transitions.
    477      */
    478     private boolean completeAdd(PendingAddArguments args) {
    479         boolean result = false;
    480         switch (args.requestCode) {
    481             case REQUEST_PICK_APPLICATION:
    482                 completeAddApplication(args.intent, args.container, args.screen, args.cellX,
    483                         args.cellY);
    484                 break;
    485             case REQUEST_PICK_SHORTCUT:
    486                 processShortcut(args.intent);
    487                 break;
    488             case REQUEST_CREATE_SHORTCUT:
    489                 completeAddShortcut(args.intent, args.container, args.screen, args.cellX,
    490                         args.cellY);
    491                 result = true;
    492                 break;
    493             case REQUEST_PICK_APPWIDGET:
    494                 addAppWidgetFromPick(args.intent);
    495                 break;
    496             case REQUEST_CREATE_APPWIDGET:
    497                 int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
    498                 completeAddAppWidget(appWidgetId, args.container, args.screen);
    499                 result = true;
    500                 break;
    501             case REQUEST_PICK_WALLPAPER:
    502                 // We just wanted the activity result here so we can clear mWaitingForResult
    503                 break;
    504         }
    505         // In any situation where we have a multi-step drop, we should reset the add info only after
    506         // we complete the drop
    507         resetAddInfo();
    508         return result;
    509     }
    510 
    511     @Override
    512     protected void onActivityResult(final int requestCode, int resultCode, final Intent data) {
    513         boolean delayExitSpringLoadedMode = false;
    514         mWaitingForResult = false;
    515 
    516         // The pattern used here is that a user PICKs a specific application,
    517         // which, depending on the target, might need to CREATE the actual target.
    518 
    519         // For example, the user would PICK_SHORTCUT for "Music playlist", and we
    520         // launch over to the Music app to actually CREATE_SHORTCUT.
    521         if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
    522             final PendingAddArguments args = new PendingAddArguments();
    523             args.requestCode = requestCode;
    524             args.intent = data;
    525             args.container = mPendingAddInfo.container;
    526             args.screen = mPendingAddInfo.screen;
    527             args.cellX = mPendingAddInfo.cellX;
    528             args.cellY = mPendingAddInfo.cellY;
    529 
    530             // If the loader is still running, defer the add until it is done.
    531             if (isWorkspaceLocked()) {
    532                 sPendingAddList.add(args);
    533             } else {
    534                 delayExitSpringLoadedMode = completeAdd(args);
    535             }
    536         } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
    537                 requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED) {
    538             if (data != null) {
    539                 // Clean up the appWidgetId if we canceled
    540                 int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
    541                 if (appWidgetId != -1) {
    542                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
    543                 }
    544             }
    545         }
    546 
    547         // Exit spring loaded mode if necessary after cancelling the configuration of a widget
    548         exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode);
    549     }
    550 
    551     @Override
    552     protected void onResume() {
    553         super.onResume();
    554         mPaused = false;
    555         if (mRestoring || mOnResumeNeedsLoad) {
    556             mWorkspaceLoading = true;
    557             mModel.startLoader(this, true);
    558             mRestoring = false;
    559             mOnResumeNeedsLoad = false;
    560         }
    561         if (mWaitingForResume != null) {
    562             mWaitingForResume.setStayPressed(false);
    563         }
    564         // When we resume Launcher, a different Activity might be responsible for the app
    565         // market intent, so refresh the icon
    566         updateAppMarketIcon();
    567         mAppsCustomizeTabHost.onResume();
    568         if (!mWorkspaceLoading) {
    569             final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
    570             final Workspace workspace = mWorkspace;
    571             // We want to let Launcher draw itself at least once before we force it to build
    572             // layers on all the workspace pages, so that transitioning to Launcher from other
    573             // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to
    574             // a true draw so we wait until the second preDraw call to be safe
    575             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    576                 boolean mFirstTime = true;
    577                 public boolean onPreDraw() {
    578                     if (mFirstTime) {
    579                         mFirstTime = false;
    580                     } else {
    581                         workspace.post(mBuildLayersRunnable);
    582                         observer.removeOnPreDrawListener(this);
    583                     }
    584                     return true;
    585                 }
    586             });
    587         }
    588         clearTypedText();
    589     }
    590 
    591     @Override
    592     protected void onPause() {
    593         super.onPause();
    594         mPaused = true;
    595         mDragController.cancelDrag();
    596     }
    597 
    598     @Override
    599     public Object onRetainNonConfigurationInstance() {
    600         // Flag the loader to stop early before switching
    601         mModel.stopLoader();
    602         if (mAppsCustomizeContent != null) {
    603             mAppsCustomizeContent.surrender();
    604         }
    605         return Boolean.TRUE;
    606     }
    607 
    608     // We can't hide the IME if it was forced open.  So don't bother
    609     /*
    610     @Override
    611     public void onWindowFocusChanged(boolean hasFocus) {
    612         super.onWindowFocusChanged(hasFocus);
    613 
    614         if (hasFocus) {
    615             final InputMethodManager inputManager = (InputMethodManager)
    616                     getSystemService(Context.INPUT_METHOD_SERVICE);
    617             WindowManager.LayoutParams lp = getWindow().getAttributes();
    618             inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
    619                         android.os.Handler()) {
    620                         protected void onReceiveResult(int resultCode, Bundle resultData) {
    621                             Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
    622                         }
    623                     });
    624             Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
    625         }
    626     }
    627     */
    628 
    629     private boolean acceptFilter() {
    630         final InputMethodManager inputManager = (InputMethodManager)
    631                 getSystemService(Context.INPUT_METHOD_SERVICE);
    632         return !inputManager.isFullscreenMode();
    633     }
    634 
    635     @Override
    636     public boolean onKeyDown(int keyCode, KeyEvent event) {
    637         final int uniChar = event.getUnicodeChar();
    638         final boolean handled = super.onKeyDown(keyCode, event);
    639         final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
    640         if (!handled && acceptFilter() && isKeyNotWhitespace) {
    641             boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
    642                     keyCode, event);
    643             if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
    644                 // something usable has been typed - start a search
    645                 // the typed text will be retrieved and cleared by
    646                 // showSearchDialog()
    647                 // If there are multiple keystrokes before the search dialog takes focus,
    648                 // onSearchRequested() will be called for every keystroke,
    649                 // but it is idempotent, so it's fine.
    650                 return onSearchRequested();
    651             }
    652         }
    653 
    654         // Eat the long press event so the keyboard doesn't come up.
    655         if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
    656             return true;
    657         }
    658 
    659         return handled;
    660     }
    661 
    662     private String getTypedText() {
    663         return mDefaultKeySsb.toString();
    664     }
    665 
    666     private void clearTypedText() {
    667         mDefaultKeySsb.clear();
    668         mDefaultKeySsb.clearSpans();
    669         Selection.setSelection(mDefaultKeySsb, 0);
    670     }
    671 
    672     /**
    673      * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
    674      * State
    675      */
    676     private static State intToState(int stateOrdinal) {
    677         State state = State.WORKSPACE;
    678         final State[] stateValues = State.values();
    679         for (int i = 0; i < stateValues.length; i++) {
    680             if (stateValues[i].ordinal() == stateOrdinal) {
    681                 state = stateValues[i];
    682                 break;
    683             }
    684         }
    685         return state;
    686     }
    687 
    688     /**
    689      * Restores the previous state, if it exists.
    690      *
    691      * @param savedState The previous state.
    692      */
    693     private void restoreState(Bundle savedState) {
    694         if (savedState == null) {
    695             return;
    696         }
    697 
    698         State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
    699         if (state == State.APPS_CUSTOMIZE) {
    700             showAllApps(false);
    701         }
    702 
    703         final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
    704         if (currentScreen > -1) {
    705             mWorkspace.setCurrentPage(currentScreen);
    706         }
    707 
    708         final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
    709         final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
    710 
    711         if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
    712             mPendingAddInfo.container = pendingAddContainer;
    713             mPendingAddInfo.screen = pendingAddScreen;
    714             mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
    715             mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
    716             mRestoring = true;
    717         }
    718 
    719         boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
    720         if (renameFolder) {
    721             long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
    722             mFolderInfo = mModel.getFolderById(this, sFolders, id);
    723             mRestoring = true;
    724         }
    725 
    726 
    727         // Restore the AppsCustomize tab
    728         if (mAppsCustomizeTabHost != null) {
    729             String curTab = savedState.getString("apps_customize_currentTab");
    730             if (curTab != null) {
    731                 // We set this directly so that there is no delay before the tab is set
    732                 mAppsCustomizeContent.setContentType(
    733                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
    734                 mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
    735                 mAppsCustomizeContent.loadAssociatedPages(
    736                         mAppsCustomizeContent.getCurrentPage());
    737             }
    738 
    739             int currentIndex = savedState.getInt("apps_customize_currentIndex");
    740             mAppsCustomizeContent.restorePageForIndex(currentIndex);
    741         }
    742     }
    743 
    744     /**
    745      * Finds all the views we need and configure them properly.
    746      */
    747     private void setupViews() {
    748         final DragController dragController = mDragController;
    749 
    750         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
    751         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
    752         mQsbDivider = (ImageView) findViewById(R.id.qsb_divider);
    753         mDockDivider = (ImageView) findViewById(R.id.dock_divider);
    754 
    755         // Setup the drag layer
    756         mDragLayer.setup(this, dragController);
    757 
    758         // Setup the hotseat
    759         mHotseat = (Hotseat) findViewById(R.id.hotseat);
    760         if (mHotseat != null) {
    761             mHotseat.setup(this);
    762         }
    763 
    764         // Setup the workspace
    765         mWorkspace.setHapticFeedbackEnabled(false);
    766         mWorkspace.setOnLongClickListener(this);
    767         mWorkspace.setup(dragController);
    768         dragController.addDragListener(mWorkspace);
    769 
    770         // Get the search/delete bar
    771         mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
    772 
    773         // Setup AppsCustomize
    774         mAppsCustomizeTabHost = (AppsCustomizeTabHost)
    775                 findViewById(R.id.apps_customize_pane);
    776         mAppsCustomizeContent = (AppsCustomizePagedView)
    777                 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
    778         mAppsCustomizeContent.setup(this, dragController);
    779 
    780         // Get the all apps button
    781         mAllAppsButton = findViewById(R.id.all_apps_button);
    782         if (mAllAppsButton != null) {
    783             mAllAppsButton.setOnTouchListener(new View.OnTouchListener() {
    784                 @Override
    785                 public boolean onTouch(View v, MotionEvent event) {
    786                     if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
    787                         onTouchDownAllAppsButton(v);
    788                     }
    789                     return false;
    790                 }
    791             });
    792         }
    793         // Setup the drag controller (drop targets have to be added in reverse order in priority)
    794         dragController.setDragScoller(mWorkspace);
    795         dragController.setScrollView(mDragLayer);
    796         dragController.setMoveTarget(mWorkspace);
    797         dragController.addDropTarget(mWorkspace);
    798         if (mSearchDropTargetBar != null) {
    799             mSearchDropTargetBar.setup(this, dragController);
    800         }
    801     }
    802 
    803     /**
    804      * Creates a view representing a shortcut.
    805      *
    806      * @param info The data structure describing the shortcut.
    807      *
    808      * @return A View inflated from R.layout.application.
    809      */
    810     View createShortcut(ShortcutInfo info) {
    811         return createShortcut(R.layout.application,
    812                 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
    813     }
    814 
    815     /**
    816      * Creates a view representing a shortcut inflated from the specified resource.
    817      *
    818      * @param layoutResId The id of the XML layout used to create the shortcut.
    819      * @param parent The group the shortcut belongs to.
    820      * @param info The data structure describing the shortcut.
    821      *
    822      * @return A View inflated from layoutResId.
    823      */
    824     View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
    825         BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
    826         favorite.applyFromShortcutInfo(info, mIconCache);
    827         favorite.setOnClickListener(this);
    828         return favorite;
    829     }
    830 
    831     /**
    832      * Add an application shortcut to the workspace.
    833      *
    834      * @param data The intent describing the application.
    835      * @param cellInfo The position on screen where to create the shortcut.
    836      */
    837     void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) {
    838         final int[] cellXY = mTmpAddItemCellCoordinates;
    839         final CellLayout layout = getCellLayout(container, screen);
    840 
    841         // First we check if we already know the exact location where we want to add this item.
    842         if (cellX >= 0 && cellY >= 0) {
    843             cellXY[0] = cellX;
    844             cellXY[1] = cellY;
    845         } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
    846             showOutOfSpaceMessage();
    847             return;
    848         }
    849 
    850         final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
    851 
    852         if (info != null) {
    853             info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
    854                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    855             info.container = ItemInfo.NO_ID;
    856             mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1],
    857                     isWorkspaceLocked(), cellX, cellY);
    858         } else {
    859             Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
    860         }
    861     }
    862 
    863     /**
    864      * Add a shortcut to the workspace.
    865      *
    866      * @param data The intent describing the shortcut.
    867      * @param cellInfo The position on screen where to create the shortcut.
    868      */
    869     private void completeAddShortcut(Intent data, long container, int screen, int cellX,
    870             int cellY) {
    871         int[] cellXY = mTmpAddItemCellCoordinates;
    872         int[] touchXY = mPendingAddInfo.dropPos;
    873         CellLayout layout = getCellLayout(container, screen);
    874 
    875         boolean foundCellSpan = false;
    876 
    877         ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
    878         if (info == null) {
    879             return;
    880         }
    881         final View view = createShortcut(info);
    882 
    883         // First we check if we already know the exact location where we want to add this item.
    884         if (cellX >= 0 && cellY >= 0) {
    885             cellXY[0] = cellX;
    886             cellXY[1] = cellY;
    887             foundCellSpan = true;
    888 
    889             // If appropriate, either create a folder or add to an existing folder
    890             if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY,
    891                     true, null,null)) {
    892                 return;
    893             }
    894             DragObject dragObject = new DragObject();
    895             dragObject.dragInfo = info;
    896             if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, dragObject, true)) {
    897                 return;
    898             }
    899         } else if (touchXY != null) {
    900             // when dragging and dropping, just find the closest free spot
    901             int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
    902             foundCellSpan = (result != null);
    903         } else {
    904             foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
    905         }
    906 
    907         if (!foundCellSpan) {
    908             showOutOfSpaceMessage();
    909             return;
    910         }
    911 
    912         LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false);
    913 
    914         if (!mRestoring) {
    915             mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1,
    916                     isWorkspaceLocked());
    917         }
    918     }
    919 
    920     int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight, int[] spanXY) {
    921         if (spanXY == null) {
    922             spanXY = new int[2];
    923         }
    924 
    925         Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
    926         // We want to account for the extra amount of padding that we are adding to the widget
    927         // to ensure that it gets the full amount of space that it has requested
    928         int requiredWidth = minWidth + padding.left + padding.right;
    929         int requiredHeight = minHeight + padding.top + padding.bottom;
    930         return CellLayout.rectToCell(getResources(), requiredWidth, requiredHeight, null);
    931     }
    932 
    933     int[] getSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
    934         return getSpanForWidget(info.provider, info.minWidth, info.minHeight, spanXY);
    935     }
    936 
    937     int[] getMinResizeSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
    938         return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight, spanXY);
    939     }
    940 
    941     int[] getSpanForWidget(PendingAddWidgetInfo info, int[] spanXY) {
    942         return getSpanForWidget(info.componentName, info.minWidth, info.minHeight, spanXY);
    943     }
    944 
    945     /**
    946      * Add a widget to the workspace.
    947      *
    948      * @param appWidgetId The app widget id
    949      * @param cellInfo The position on screen where to create the widget.
    950      */
    951     private void completeAddAppWidget(final int appWidgetId, long container, int screen) {
    952         AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
    953 
    954         // Calculate the grid spans needed to fit this widget
    955         CellLayout layout = getCellLayout(container, screen);
    956 
    957         int[] spanXY = getSpanForWidget(appWidgetInfo, null);
    958 
    959         // Try finding open space on Launcher screen
    960         // We have saved the position to which the widget was dragged-- this really only matters
    961         // if we are placing widgets on a "spring-loaded" screen
    962         int[] cellXY = mTmpAddItemCellCoordinates;
    963         int[] touchXY = mPendingAddInfo.dropPos;
    964         boolean foundCellSpan = false;
    965         if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
    966             cellXY[0] = mPendingAddInfo.cellX;
    967             cellXY[1] = mPendingAddInfo.cellY;
    968             foundCellSpan = true;
    969         } else if (touchXY != null) {
    970             // when dragging and dropping, just find the closest free spot
    971             int[] result = layout.findNearestVacantArea(
    972                     touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
    973             foundCellSpan = (result != null);
    974         } else {
    975             foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]);
    976         }
    977 
    978         if (!foundCellSpan) {
    979             if (appWidgetId != -1) {
    980                 // Deleting an app widget ID is a void call but writes to disk before returning
    981                 // to the caller...
    982                 new Thread("deleteAppWidgetId") {
    983                     public void run() {
    984                         mAppWidgetHost.deleteAppWidgetId(appWidgetId);
    985                     }
    986                 }.start();
    987             }
    988             showOutOfSpaceMessage();
    989             return;
    990         }
    991 
    992         // Build Launcher-specific widget info and save to database
    993         LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
    994         launcherInfo.spanX = spanXY[0];
    995         launcherInfo.spanY = spanXY[1];
    996 
    997         LauncherModel.addItemToDatabase(this, launcherInfo,
    998                 container, screen, cellXY[0], cellXY[1], false);
    999 
   1000         if (!mRestoring) {
   1001             // Perform actual inflation because we're live
   1002             launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
   1003 
   1004             launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
   1005             launcherInfo.hostView.setTag(launcherInfo);
   1006 
   1007             mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
   1008                     launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
   1009 
   1010             addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
   1011         }
   1012     }
   1013 
   1014     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   1015         @Override
   1016         public void onReceive(Context context, Intent intent) {
   1017             final String action = intent.getAction();
   1018             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
   1019                 mUserPresent = false;
   1020                 mDragLayer.clearAllResizeFrames();
   1021                 updateRunning();
   1022 
   1023                 // Reset AllApps to its initial state only if we are not in the middle of
   1024                 // processing a multi-step drop
   1025                 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
   1026                     mAppsCustomizeTabHost.reset();
   1027                     showWorkspace(false);
   1028                 }
   1029             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
   1030                 mUserPresent = true;
   1031                 updateRunning();
   1032             }
   1033         }
   1034     };
   1035 
   1036     @Override
   1037     public void onAttachedToWindow() {
   1038         super.onAttachedToWindow();
   1039 
   1040         // Listen for broadcasts related to user-presence
   1041         final IntentFilter filter = new IntentFilter();
   1042         filter.addAction(Intent.ACTION_SCREEN_OFF);
   1043         filter.addAction(Intent.ACTION_USER_PRESENT);
   1044         registerReceiver(mReceiver, filter);
   1045 
   1046         mAttached = true;
   1047         mVisible = true;
   1048     }
   1049 
   1050     @Override
   1051     public void onDetachedFromWindow() {
   1052         super.onDetachedFromWindow();
   1053         mVisible = false;
   1054         mDragLayer.clearAllResizeFrames();
   1055 
   1056         if (mAttached) {
   1057             unregisterReceiver(mReceiver);
   1058             mAttached = false;
   1059         }
   1060         updateRunning();
   1061     }
   1062 
   1063     public void onWindowVisibilityChanged(int visibility) {
   1064         mVisible = visibility == View.VISIBLE;
   1065         updateRunning();
   1066     }
   1067 
   1068     private void sendAdvanceMessage(long delay) {
   1069         mHandler.removeMessages(ADVANCE_MSG);
   1070         Message msg = mHandler.obtainMessage(ADVANCE_MSG);
   1071         mHandler.sendMessageDelayed(msg, delay);
   1072         mAutoAdvanceSentTime = System.currentTimeMillis();
   1073     }
   1074 
   1075     private void updateRunning() {
   1076         boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
   1077         if (autoAdvanceRunning != mAutoAdvanceRunning) {
   1078             mAutoAdvanceRunning = autoAdvanceRunning;
   1079             if (autoAdvanceRunning) {
   1080                 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
   1081                 sendAdvanceMessage(delay);
   1082             } else {
   1083                 if (!mWidgetsToAdvance.isEmpty()) {
   1084                     mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
   1085                             (System.currentTimeMillis() - mAutoAdvanceSentTime));
   1086                 }
   1087                 mHandler.removeMessages(ADVANCE_MSG);
   1088                 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
   1089             }
   1090         }
   1091     }
   1092 
   1093     private final Handler mHandler = new Handler() {
   1094         @Override
   1095         public void handleMessage(Message msg) {
   1096             if (msg.what == ADVANCE_MSG) {
   1097                 int i = 0;
   1098                 for (View key: mWidgetsToAdvance.keySet()) {
   1099                     final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
   1100                     final int delay = mAdvanceStagger * i;
   1101                     if (v instanceof Advanceable) {
   1102                        postDelayed(new Runnable() {
   1103                            public void run() {
   1104                                ((Advanceable) v).advance();
   1105                            }
   1106                        }, delay);
   1107                     }
   1108                     i++;
   1109                 }
   1110                 sendAdvanceMessage(mAdvanceInterval);
   1111             }
   1112         }
   1113     };
   1114 
   1115     void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
   1116         if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
   1117         View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
   1118         if (v instanceof Advanceable) {
   1119             mWidgetsToAdvance.put(hostView, appWidgetInfo);
   1120             ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
   1121             updateRunning();
   1122         }
   1123     }
   1124 
   1125     void removeWidgetToAutoAdvance(View hostView) {
   1126         if (mWidgetsToAdvance.containsKey(hostView)) {
   1127             mWidgetsToAdvance.remove(hostView);
   1128             updateRunning();
   1129         }
   1130     }
   1131 
   1132     public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
   1133         removeWidgetToAutoAdvance(launcherInfo.hostView);
   1134         launcherInfo.hostView = null;
   1135     }
   1136 
   1137     void showOutOfSpaceMessage() {
   1138         Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
   1139     }
   1140 
   1141     public LauncherAppWidgetHost getAppWidgetHost() {
   1142         return mAppWidgetHost;
   1143     }
   1144 
   1145     public LauncherModel getModel() {
   1146         return mModel;
   1147     }
   1148 
   1149     void closeSystemDialogs() {
   1150         getWindow().closeAllPanels();
   1151 
   1152         /**
   1153          * We should remove this code when we remove all the dialog code.
   1154         try {
   1155             dismissDialog(DIALOG_CREATE_SHORTCUT);
   1156             // Unlock the workspace if the dialog was showing
   1157         } catch (Exception e) {
   1158             // An exception is thrown if the dialog is not visible, which is fine
   1159         }
   1160 
   1161         try {
   1162             dismissDialog(DIALOG_RENAME_FOLDER);
   1163             // Unlock the workspace if the dialog was showing
   1164         } catch (Exception e) {
   1165             // An exception is thrown if the dialog is not visible, which is fine
   1166         }
   1167          */
   1168 
   1169         // Whatever we were doing is hereby canceled.
   1170         mWaitingForResult = false;
   1171     }
   1172 
   1173     @Override
   1174     protected void onNewIntent(Intent intent) {
   1175         super.onNewIntent(intent);
   1176 
   1177         // Close the menu
   1178         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
   1179             // also will cancel mWaitingForResult.
   1180             closeSystemDialogs();
   1181 
   1182             boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
   1183                         != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
   1184 
   1185             Folder openFolder = mWorkspace.getOpenFolder();
   1186             // In all these cases, only animate if we're already on home
   1187             mWorkspace.exitWidgetResizeMode();
   1188             if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
   1189                     openFolder == null) {
   1190                 mWorkspace.moveToDefaultScreen(true);
   1191             }
   1192 
   1193             closeFolder();
   1194             exitSpringLoadedDragMode();
   1195             showWorkspace(alreadyOnHome);
   1196 
   1197             final View v = getWindow().peekDecorView();
   1198             if (v != null && v.getWindowToken() != null) {
   1199                 InputMethodManager imm = (InputMethodManager)getSystemService(
   1200                         INPUT_METHOD_SERVICE);
   1201                 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
   1202             }
   1203 
   1204             // Reset AllApps to its initial state
   1205             if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
   1206                 mAppsCustomizeTabHost.reset();
   1207             }
   1208         }
   1209     }
   1210 
   1211     @Override
   1212     protected void onRestoreInstanceState(Bundle savedInstanceState) {
   1213         // Do not call super here
   1214         mSavedInstanceState = savedInstanceState;
   1215     }
   1216 
   1217     @Override
   1218     protected void onSaveInstanceState(Bundle outState) {
   1219         outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
   1220         super.onSaveInstanceState(outState);
   1221 
   1222         outState.putInt(RUNTIME_STATE, mState.ordinal());
   1223         // We close any open folder since it will not be re-opened, and we need to make sure
   1224         // this state is reflected.
   1225         closeFolder();
   1226 
   1227         if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 &&
   1228                 mWaitingForResult) {
   1229             outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
   1230             outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen);
   1231             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
   1232             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
   1233         }
   1234 
   1235         if (mFolderInfo != null && mWaitingForResult) {
   1236             outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
   1237             outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
   1238         }
   1239 
   1240         // Save the current AppsCustomize tab
   1241         if (mAppsCustomizeTabHost != null) {
   1242             String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
   1243             if (currentTabTag != null) {
   1244                 outState.putString("apps_customize_currentTab", currentTabTag);
   1245             }
   1246             int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
   1247             outState.putInt("apps_customize_currentIndex", currentIndex);
   1248         }
   1249     }
   1250 
   1251     @Override
   1252     public void onDestroy() {
   1253         super.onDestroy();
   1254 
   1255         // Remove all pending runnables
   1256         mHandler.removeMessages(ADVANCE_MSG);
   1257         mHandler.removeMessages(0);
   1258         mWorkspace.removeCallbacks(mBuildLayersRunnable);
   1259 
   1260         // Stop callbacks from LauncherModel
   1261         LauncherApplication app = ((LauncherApplication) getApplication());
   1262         mModel.stopLoader();
   1263         app.setLauncher(null);
   1264 
   1265         try {
   1266             mAppWidgetHost.stopListening();
   1267         } catch (NullPointerException ex) {
   1268             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
   1269         }
   1270         mAppWidgetHost = null;
   1271 
   1272         mWidgetsToAdvance.clear();
   1273 
   1274         TextKeyListener.getInstance().release();
   1275 
   1276 
   1277         unbindWorkspaceAndHotseatItems();
   1278 
   1279         getContentResolver().unregisterContentObserver(mWidgetObserver);
   1280         unregisterReceiver(mCloseSystemDialogsReceiver);
   1281 
   1282         ((ViewGroup) mWorkspace.getParent()).removeAllViews();
   1283         mWorkspace.removeAllViews();
   1284         mWorkspace = null;
   1285         mDragController = null;
   1286 
   1287         ValueAnimator.clearAllAnimations();
   1288     }
   1289 
   1290     public DragController getDragController() {
   1291         return mDragController;
   1292     }
   1293 
   1294     @Override
   1295     public void startActivityForResult(Intent intent, int requestCode) {
   1296         if (requestCode >= 0) mWaitingForResult = true;
   1297         super.startActivityForResult(intent, requestCode);
   1298     }
   1299 
   1300     /**
   1301      * Indicates that we want global search for this activity by setting the globalSearch
   1302      * argument for {@link #startSearch} to true.
   1303      */
   1304     @Override
   1305     public void startSearch(String initialQuery, boolean selectInitialQuery,
   1306             Bundle appSearchData, boolean globalSearch) {
   1307 
   1308         showWorkspace(true);
   1309 
   1310         if (initialQuery == null) {
   1311             // Use any text typed in the launcher as the initial query
   1312             initialQuery = getTypedText();
   1313         }
   1314         if (appSearchData == null) {
   1315             appSearchData = new Bundle();
   1316             appSearchData.putString(Search.SOURCE, "launcher-search");
   1317         }
   1318         Rect sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
   1319 
   1320         final SearchManager searchManager =
   1321                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
   1322         searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
   1323             appSearchData, globalSearch, sourceBounds);
   1324     }
   1325 
   1326     @Override
   1327     public boolean onCreateOptionsMenu(Menu menu) {
   1328         if (isWorkspaceLocked()) {
   1329             return false;
   1330         }
   1331 
   1332         super.onCreateOptionsMenu(menu);
   1333 
   1334         Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS);
   1335         manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
   1336                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
   1337         Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
   1338         settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
   1339                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
   1340         String helpUrl = getString(R.string.help_url);
   1341         Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl));
   1342         help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
   1343                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
   1344 
   1345         menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
   1346             .setIcon(android.R.drawable.ic_menu_gallery)
   1347             .setAlphabeticShortcut('W');
   1348         menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
   1349             .setIcon(android.R.drawable.ic_menu_manage)
   1350             .setIntent(manageApps)
   1351             .setAlphabeticShortcut('M');
   1352         menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings)
   1353             .setIcon(android.R.drawable.ic_menu_preferences)
   1354             .setIntent(settings)
   1355             .setAlphabeticShortcut('P');
   1356         if (!helpUrl.isEmpty()) {
   1357             menu.add(0, MENU_HELP, 0, R.string.menu_help)
   1358                 .setIcon(android.R.drawable.ic_menu_help)
   1359                 .setIntent(help)
   1360                 .setAlphabeticShortcut('H');
   1361         }
   1362         return true;
   1363     }
   1364 
   1365     @Override
   1366     public boolean onPrepareOptionsMenu(Menu menu) {
   1367         super.onPrepareOptionsMenu(menu);
   1368 
   1369         if (mAppsCustomizeTabHost.isTransitioning()) {
   1370             return false;
   1371         }
   1372         boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE);
   1373         menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible);
   1374 
   1375         return true;
   1376     }
   1377 
   1378     @Override
   1379     public boolean onOptionsItemSelected(MenuItem item) {
   1380         switch (item.getItemId()) {
   1381         case MENU_WALLPAPER_SETTINGS:
   1382             startWallpaper();
   1383             return true;
   1384         }
   1385 
   1386         return super.onOptionsItemSelected(item);
   1387     }
   1388 
   1389     @Override
   1390     public boolean onSearchRequested() {
   1391         startSearch(null, false, null, true);
   1392         // Use a custom animation for launching search
   1393         overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
   1394         return true;
   1395     }
   1396 
   1397     public boolean isWorkspaceLocked() {
   1398         return mWorkspaceLoading || mWaitingForResult;
   1399     }
   1400 
   1401     private void resetAddInfo() {
   1402         mPendingAddInfo.container = ItemInfo.NO_ID;
   1403         mPendingAddInfo.screen = -1;
   1404         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
   1405         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
   1406         mPendingAddInfo.dropPos = null;
   1407     }
   1408 
   1409     void addAppWidgetFromPick(Intent data) {
   1410         // TODO: catch bad widget exception when sent
   1411         int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
   1412         // TODO: Is this log message meaningful?
   1413         if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras());
   1414         addAppWidgetImpl(appWidgetId, null);
   1415     }
   1416 
   1417     void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
   1418         AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
   1419 
   1420         if (appWidget.configure != null) {
   1421             // Launch over to configure widget, if needed
   1422             Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
   1423             intent.setComponent(appWidget.configure);
   1424             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
   1425             if (info != null) {
   1426                 if (info.mimeType != null && !info.mimeType.isEmpty()) {
   1427                     intent.putExtra(
   1428                             InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
   1429                             info.mimeType);
   1430 
   1431                     final String mimeType = info.mimeType;
   1432                     final ClipData clipData = (ClipData) info.configurationData;
   1433                     final ClipDescription clipDesc = clipData.getDescription();
   1434                     for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
   1435                         if (clipDesc.getMimeType(i).equals(mimeType)) {
   1436                             final ClipData.Item item = clipData.getItemAt(i);
   1437                             final CharSequence stringData = item.getText();
   1438                             final Uri uriData = item.getUri();
   1439                             final Intent intentData = item.getIntent();
   1440                             final String key =
   1441                                 InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
   1442                             if (uriData != null) {
   1443                                 intent.putExtra(key, uriData);
   1444                             } else if (intentData != null) {
   1445                                 intent.putExtra(key, intentData);
   1446                             } else if (stringData != null) {
   1447                                 intent.putExtra(key, stringData);
   1448                             }
   1449                             break;
   1450                         }
   1451                     }
   1452                 }
   1453             }
   1454 
   1455             startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
   1456         } else {
   1457             // Otherwise just add it
   1458             completeAddAppWidget(appWidgetId, info.container, info.screen);
   1459 
   1460             // Exit spring loaded mode if necessary after adding the widget
   1461             exitSpringLoadedDragModeDelayed(true, false);
   1462         }
   1463     }
   1464 
   1465     /**
   1466      * Process a shortcut drop.
   1467      *
   1468      * @param componentName The name of the component
   1469      * @param screen The screen where it should be added
   1470      * @param cell The cell it should be added to, optional
   1471      * @param position The location on the screen where it was dropped, optional
   1472      */
   1473     void processShortcutFromDrop(ComponentName componentName, long container, int screen,
   1474             int[] cell, int[] loc) {
   1475         resetAddInfo();
   1476         mPendingAddInfo.container = container;
   1477         mPendingAddInfo.screen = screen;
   1478         mPendingAddInfo.dropPos = loc;
   1479 
   1480         if (cell != null) {
   1481             mPendingAddInfo.cellX = cell[0];
   1482             mPendingAddInfo.cellY = cell[1];
   1483         }
   1484 
   1485         Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
   1486         createShortcutIntent.setComponent(componentName);
   1487         processShortcut(createShortcutIntent);
   1488     }
   1489 
   1490     /**
   1491      * Process a widget drop.
   1492      *
   1493      * @param info The PendingAppWidgetInfo of the widget being added.
   1494      * @param screen The screen where it should be added
   1495      * @param cell The cell it should be added to, optional
   1496      * @param position The location on the screen where it was dropped, optional
   1497      */
   1498     void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen,
   1499             int[] cell, int[] loc) {
   1500         resetAddInfo();
   1501         mPendingAddInfo.container = info.container = container;
   1502         mPendingAddInfo.screen = info.screen = screen;
   1503         mPendingAddInfo.dropPos = loc;
   1504         if (cell != null) {
   1505             mPendingAddInfo.cellX = cell[0];
   1506             mPendingAddInfo.cellY = cell[1];
   1507         }
   1508 
   1509         int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
   1510         AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
   1511         addAppWidgetImpl(appWidgetId, info);
   1512     }
   1513 
   1514     void processShortcut(Intent intent) {
   1515         // Handle case where user selected "Applications"
   1516         String applicationName = getResources().getString(R.string.group_applications);
   1517         String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
   1518 
   1519         if (applicationName != null && applicationName.equals(shortcutName)) {
   1520             Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
   1521             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
   1522 
   1523             Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
   1524             pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
   1525             pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
   1526             startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
   1527         } else {
   1528             startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
   1529         }
   1530     }
   1531 
   1532     void processWallpaper(Intent intent) {
   1533         startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
   1534     }
   1535 
   1536     FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX,
   1537             int cellY) {
   1538         final FolderInfo folderInfo = new FolderInfo();
   1539         folderInfo.title = getText(R.string.folder_name);
   1540 
   1541         // Update the model
   1542         LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY,
   1543                 false);
   1544         sFolders.put(folderInfo.id, folderInfo);
   1545 
   1546         // Create the view
   1547         FolderIcon newFolder =
   1548             FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
   1549         mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1,
   1550                 isWorkspaceLocked());
   1551         return newFolder;
   1552     }
   1553 
   1554     void removeFolder(FolderInfo folder) {
   1555         sFolders.remove(folder.id);
   1556     }
   1557 
   1558     private void startWallpaper() {
   1559         showWorkspace(true);
   1560         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
   1561         Intent chooser = Intent.createChooser(pickWallpaper,
   1562                 getText(R.string.chooser_wallpaper));
   1563         // NOTE: Adds a configure option to the chooser if the wallpaper supports it
   1564         //       Removed in Eclair MR1
   1565 //        WallpaperManager wm = (WallpaperManager)
   1566 //                getSystemService(Context.WALLPAPER_SERVICE);
   1567 //        WallpaperInfo wi = wm.getWallpaperInfo();
   1568 //        if (wi != null && wi.getSettingsActivity() != null) {
   1569 //            LabeledIntent li = new LabeledIntent(getPackageName(),
   1570 //                    R.string.configure_wallpaper, 0);
   1571 //            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
   1572 //            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
   1573 //        }
   1574         startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
   1575     }
   1576 
   1577     /**
   1578      * Registers various content observers. The current implementation registers
   1579      * only a favorites observer to keep track of the favorites applications.
   1580      */
   1581     private void registerContentObservers() {
   1582         ContentResolver resolver = getContentResolver();
   1583         resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
   1584                 true, mWidgetObserver);
   1585     }
   1586 
   1587     @Override
   1588     public boolean dispatchKeyEvent(KeyEvent event) {
   1589         if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1590             switch (event.getKeyCode()) {
   1591                 case KeyEvent.KEYCODE_HOME:
   1592                     return true;
   1593                 case KeyEvent.KEYCODE_VOLUME_DOWN:
   1594                     if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) {
   1595                         dumpState();
   1596                         return true;
   1597                     }
   1598                     break;
   1599             }
   1600         } else if (event.getAction() == KeyEvent.ACTION_UP) {
   1601             switch (event.getKeyCode()) {
   1602                 case KeyEvent.KEYCODE_HOME:
   1603                     return true;
   1604             }
   1605         }
   1606 
   1607         return super.dispatchKeyEvent(event);
   1608     }
   1609 
   1610     @Override
   1611     public void onBackPressed() {
   1612         if (mState == State.APPS_CUSTOMIZE) {
   1613             showWorkspace(true);
   1614         } else if (mWorkspace.getOpenFolder() != null) {
   1615             Folder openFolder = mWorkspace.getOpenFolder();
   1616             if (openFolder.isEditingName()) {
   1617                 openFolder.dismissEditingName();
   1618             } else {
   1619                 closeFolder();
   1620             }
   1621         } else {
   1622             mWorkspace.exitWidgetResizeMode();
   1623 
   1624             // Back button is a no-op here, but give at least some feedback for the button press
   1625             mWorkspace.showOutlinesTemporarily();
   1626         }
   1627     }
   1628 
   1629     /**
   1630      * Re-listen when widgets are reset.
   1631      */
   1632     private void onAppWidgetReset() {
   1633         if (mAppWidgetHost != null) {
   1634             mAppWidgetHost.startListening();
   1635         }
   1636     }
   1637 
   1638     /**
   1639      * Go through the and disconnect any of the callbacks in the drawables and the views or we
   1640      * leak the previous Home screen on orientation change.
   1641      */
   1642     private void unbindWorkspaceAndHotseatItems() {
   1643         if (mModel != null) {
   1644             mModel.unbindWorkspaceItems();
   1645         }
   1646     }
   1647 
   1648     /**
   1649      * Launches the intent referred by the clicked shortcut.
   1650      *
   1651      * @param v The view representing the clicked shortcut.
   1652      */
   1653     public void onClick(View v) {
   1654         // Make sure that rogue clicks don't get through while allapps is launching, or after the
   1655         // view has detached (it's possible for this to happen if the view is removed mid touch).
   1656         if (v.getWindowToken() == null) {
   1657             return;
   1658         }
   1659 
   1660         if (mWorkspace.isSwitchingState()) {
   1661             return;
   1662         }
   1663 
   1664         Object tag = v.getTag();
   1665         if (tag instanceof ShortcutInfo) {
   1666             // Open shortcut
   1667             final Intent intent = ((ShortcutInfo) tag).intent;
   1668             int[] pos = new int[2];
   1669             v.getLocationOnScreen(pos);
   1670             intent.setSourceBounds(new Rect(pos[0], pos[1],
   1671                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));
   1672             boolean success = startActivitySafely(intent, tag);
   1673 
   1674             if (success && v instanceof BubbleTextView) {
   1675                 mWaitingForResume = (BubbleTextView) v;
   1676                 mWaitingForResume.setStayPressed(true);
   1677             }
   1678         } else if (tag instanceof FolderInfo) {
   1679             if (v instanceof FolderIcon) {
   1680                 FolderIcon fi = (FolderIcon) v;
   1681                 handleFolderClick(fi);
   1682             }
   1683         } else if (v == mAllAppsButton) {
   1684             if (mState == State.APPS_CUSTOMIZE) {
   1685                 showWorkspace(true);
   1686             } else {
   1687                 onClickAllAppsButton(v);
   1688             }
   1689         }
   1690     }
   1691 
   1692     public boolean onTouch(View v, MotionEvent event) {
   1693         // this is an intercepted event being forwarded from mWorkspace;
   1694         // clicking anywhere on the workspace causes the customization drawer to slide down
   1695         showWorkspace(true);
   1696         return false;
   1697     }
   1698 
   1699     /**
   1700      * Event handler for the search button
   1701      *
   1702      * @param v The view that was clicked.
   1703      */
   1704     public void onClickSearchButton(View v) {
   1705         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
   1706 
   1707         onSearchRequested();
   1708     }
   1709 
   1710     /**
   1711      * Event handler for the voice button
   1712      *
   1713      * @param v The view that was clicked.
   1714      */
   1715     public void onClickVoiceButton(View v) {
   1716         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
   1717 
   1718         Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
   1719         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
   1720         startActivity(intent);
   1721     }
   1722 
   1723     /**
   1724      * Event handler for the "grid" button that appears on the home screen, which
   1725      * enters all apps mode.
   1726      *
   1727      * @param v The view that was clicked.
   1728      */
   1729     public void onClickAllAppsButton(View v) {
   1730         showAllApps(true);
   1731     }
   1732 
   1733     public void onTouchDownAllAppsButton(View v) {
   1734         // Provide the same haptic feedback that the system offers for virtual keys.
   1735         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
   1736     }
   1737 
   1738     public void onClickAppMarketButton(View v) {
   1739         if (mAppMarketIntent != null) {
   1740             startActivitySafely(mAppMarketIntent, "app market");
   1741         }
   1742     }
   1743 
   1744     void startApplicationDetailsActivity(ComponentName componentName) {
   1745         String packageName = componentName.getPackageName();
   1746         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
   1747                 Uri.fromParts("package", packageName, null));
   1748         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
   1749         startActivity(intent);
   1750     }
   1751 
   1752     void startApplicationUninstallActivity(ApplicationInfo appInfo) {
   1753         if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
   1754             // System applications cannot be installed. For now, show a toast explaining that.
   1755             // We may give them the option of disabling apps this way.
   1756             int messageId = R.string.uninstall_system_app_text;
   1757             Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
   1758         } else {
   1759             String packageName = appInfo.componentName.getPackageName();
   1760             String className = appInfo.componentName.getClassName();
   1761             Intent intent = new Intent(
   1762                     Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
   1763             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
   1764                     Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
   1765             startActivity(intent);
   1766         }
   1767     }
   1768 
   1769     boolean startActivitySafely(Intent intent, Object tag) {
   1770         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1771         try {
   1772             startActivity(intent);
   1773             return true;
   1774         } catch (ActivityNotFoundException e) {
   1775             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
   1776             Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
   1777         } catch (SecurityException e) {
   1778             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
   1779             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
   1780                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
   1781                     "or use the exported attribute for this activity. "
   1782                     + "tag="+ tag + " intent=" + intent, e);
   1783         }
   1784         return false;
   1785     }
   1786 
   1787     void startActivityForResultSafely(Intent intent, int requestCode) {
   1788         try {
   1789             startActivityForResult(intent, requestCode);
   1790         } catch (ActivityNotFoundException e) {
   1791             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
   1792         } catch (SecurityException e) {
   1793             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
   1794             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
   1795                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
   1796                     "or use the exported attribute for this activity.", e);
   1797         }
   1798     }
   1799 
   1800     private void handleFolderClick(FolderIcon folderIcon) {
   1801         final FolderInfo info = folderIcon.mInfo;
   1802         Folder openFolder = mWorkspace.getFolderForTag(info);
   1803 
   1804         // If the folder info reports that the associated folder is open, then verify that
   1805         // it is actually opened. There have been a few instances where this gets out of sync.
   1806         if (info.opened && openFolder == null) {
   1807             Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
   1808                     + info.screen + " (" + info.cellX + ", " + info.cellY + ")");
   1809             info.opened = false;
   1810         }
   1811 
   1812         if (!info.opened) {
   1813             // Close any open folder
   1814             closeFolder();
   1815             // Open the requested folder
   1816             openFolder(folderIcon);
   1817         } else {
   1818             // Find the open folder...
   1819             int folderScreen;
   1820             if (openFolder != null) {
   1821                 folderScreen = mWorkspace.getPageForView(openFolder);
   1822                 // .. and close it
   1823                 closeFolder(openFolder);
   1824                 if (folderScreen != mWorkspace.getCurrentPage()) {
   1825                     // Close any folder open on the current screen
   1826                     closeFolder();
   1827                     // Pull the folder onto this screen
   1828                     openFolder(folderIcon);
   1829                 }
   1830             }
   1831         }
   1832     }
   1833 
   1834     private void growAndFadeOutFolderIcon(FolderIcon fi) {
   1835         if (fi == null) return;
   1836         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
   1837         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
   1838         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
   1839 
   1840         FolderInfo info = (FolderInfo) fi.getTag();
   1841         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   1842             CellLayout cl = (CellLayout) fi.getParent().getParent();
   1843             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
   1844             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
   1845         }
   1846 
   1847         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
   1848         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
   1849         oa.start();
   1850     }
   1851 
   1852     private void shrinkAndFadeInFolderIcon(FolderIcon fi) {
   1853         if (fi == null) return;
   1854         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
   1855         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
   1856         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
   1857 
   1858         FolderInfo info = (FolderInfo) fi.getTag();
   1859         CellLayout cl = null;
   1860         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   1861             cl = (CellLayout) fi.getParent().getParent();
   1862         }
   1863 
   1864         final CellLayout layout = cl;
   1865         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
   1866         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
   1867         oa.addListener(new AnimatorListenerAdapter() {
   1868             @Override
   1869             public void onAnimationEnd(Animator animation) {
   1870                 if (layout != null) {
   1871                     layout.clearFolderLeaveBehind();
   1872                 }
   1873             }
   1874         });
   1875         oa.start();
   1876     }
   1877 
   1878     /**
   1879      * Opens the user folder described by the specified tag. The opening of the folder
   1880      * is animated relative to the specified View. If the View is null, no animation
   1881      * is played.
   1882      *
   1883      * @param folderInfo The FolderInfo describing the folder to open.
   1884      */
   1885     public void openFolder(FolderIcon folderIcon) {
   1886         Folder folder = folderIcon.mFolder;
   1887         FolderInfo info = folder.mInfo;
   1888 
   1889         growAndFadeOutFolderIcon(folderIcon);
   1890         info.opened = true;
   1891 
   1892         // Just verify that the folder hasn't already been added to the DragLayer.
   1893         // There was a one-off crash where the folder had a parent already.
   1894         if (folder.getParent() == null) {
   1895             mDragLayer.addView(folder);
   1896             mDragController.addDropTarget((DropTarget) folder);
   1897         } else {
   1898             Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
   1899                     folder.getParent() + ").");
   1900         }
   1901         folder.animateOpen();
   1902     }
   1903 
   1904     public void closeFolder() {
   1905         Folder folder = mWorkspace.getOpenFolder();
   1906         if (folder != null) {
   1907             if (folder.isEditingName()) {
   1908                 folder.dismissEditingName();
   1909             }
   1910             closeFolder(folder);
   1911 
   1912             // Dismiss the folder cling
   1913             dismissFolderCling(null);
   1914         }
   1915     }
   1916 
   1917     void closeFolder(Folder folder) {
   1918         folder.getInfo().opened = false;
   1919 
   1920         ViewGroup parent = (ViewGroup) folder.getParent().getParent();
   1921         if (parent != null) {
   1922             FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
   1923             shrinkAndFadeInFolderIcon(fi);
   1924         }
   1925         folder.animateClosed();
   1926     }
   1927 
   1928     public boolean onLongClick(View v) {
   1929         if (mState != State.WORKSPACE) {
   1930             return false;
   1931         }
   1932 
   1933         if (isWorkspaceLocked()) {
   1934             return false;
   1935         }
   1936 
   1937         if (!(v instanceof CellLayout)) {
   1938             v = (View) v.getParent().getParent();
   1939         }
   1940 
   1941         resetAddInfo();
   1942         CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
   1943         // This happens when long clicking an item with the dpad/trackball
   1944         if (longClickCellInfo == null) {
   1945             return true;
   1946         }
   1947 
   1948         // The hotseat touch handling does not go through Workspace, and we always allow long press
   1949         // on hotseat items.
   1950         final View itemUnderLongClick = longClickCellInfo.cell;
   1951         boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
   1952         if (allowLongPress && !mDragController.isDragging()) {
   1953             if (itemUnderLongClick == null) {
   1954                 // User long pressed on empty space
   1955                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
   1956                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
   1957                 startWallpaper();
   1958             } else {
   1959                 if (!(itemUnderLongClick instanceof Folder)) {
   1960                     // User long pressed on an item
   1961                     mWorkspace.startDrag(longClickCellInfo);
   1962                 }
   1963             }
   1964         }
   1965         return true;
   1966     }
   1967 
   1968     boolean isHotseatLayout(View layout) {
   1969         return mHotseat != null && layout != null &&
   1970                 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
   1971     }
   1972     Hotseat getHotseat() {
   1973         return mHotseat;
   1974     }
   1975     SearchDropTargetBar getSearchBar() {
   1976         return mSearchDropTargetBar;
   1977     }
   1978 
   1979     /**
   1980      * Returns the CellLayout of the specified container at the specified screen.
   1981      */
   1982     CellLayout getCellLayout(long container, int screen) {
   1983         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   1984             if (mHotseat != null) {
   1985                 return mHotseat.getLayout();
   1986             } else {
   1987                 return null;
   1988             }
   1989         } else {
   1990             return (CellLayout) mWorkspace.getChildAt(screen);
   1991         }
   1992     }
   1993 
   1994     Workspace getWorkspace() {
   1995         return mWorkspace;
   1996     }
   1997 
   1998     @Override
   1999     protected Dialog onCreateDialog(int id) {
   2000         switch (id) {
   2001             case DIALOG_CREATE_SHORTCUT:
   2002                 return new CreateShortcut().createDialog();
   2003             case DIALOG_RENAME_FOLDER:
   2004                 return new RenameFolder().createDialog();
   2005         }
   2006 
   2007         return super.onCreateDialog(id);
   2008     }
   2009 
   2010     @Override
   2011     protected void onPrepareDialog(int id, Dialog dialog) {
   2012         switch (id) {
   2013             case DIALOG_CREATE_SHORTCUT:
   2014                 break;
   2015             case DIALOG_RENAME_FOLDER:
   2016                 if (mFolderInfo != null) {
   2017                     EditText input = (EditText) dialog.findViewById(R.id.folder_name);
   2018                     final CharSequence text = mFolderInfo.title;
   2019                     input.setText(text);
   2020                     input.setSelection(0, text.length());
   2021                 }
   2022                 break;
   2023         }
   2024     }
   2025 
   2026     void showRenameDialog(FolderInfo info) {
   2027         mFolderInfo = info;
   2028         mWaitingForResult = true;
   2029         showDialog(DIALOG_RENAME_FOLDER);
   2030     }
   2031 
   2032     private void showAddDialog() {
   2033         resetAddInfo();
   2034         mPendingAddInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
   2035         mPendingAddInfo.screen = mWorkspace.getCurrentPage();
   2036         mWaitingForResult = true;
   2037         showDialog(DIALOG_CREATE_SHORTCUT);
   2038     }
   2039 
   2040     private class RenameFolder {
   2041         private EditText mInput;
   2042 
   2043         Dialog createDialog() {
   2044             final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
   2045             mInput = (EditText) layout.findViewById(R.id.folder_name);
   2046 
   2047             AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
   2048             builder.setIcon(0);
   2049             builder.setTitle(getString(R.string.rename_folder_title));
   2050             builder.setCancelable(true);
   2051             builder.setOnCancelListener(new Dialog.OnCancelListener() {
   2052                 public void onCancel(DialogInterface dialog) {
   2053                     cleanup();
   2054                 }
   2055             });
   2056             builder.setNegativeButton(getString(R.string.cancel_action),
   2057                 new Dialog.OnClickListener() {
   2058                     public void onClick(DialogInterface dialog, int which) {
   2059                         cleanup();
   2060                     }
   2061                 }
   2062             );
   2063             builder.setPositiveButton(getString(R.string.rename_action),
   2064                 new Dialog.OnClickListener() {
   2065                     public void onClick(DialogInterface dialog, int which) {
   2066                         changeFolderName();
   2067                     }
   2068                 }
   2069             );
   2070             builder.setView(layout);
   2071 
   2072             final AlertDialog dialog = builder.create();
   2073             dialog.setOnShowListener(new DialogInterface.OnShowListener() {
   2074                 public void onShow(DialogInterface dialog) {
   2075                     mWaitingForResult = true;
   2076                     mInput.requestFocus();
   2077                     InputMethodManager inputManager = (InputMethodManager)
   2078                             getSystemService(Context.INPUT_METHOD_SERVICE);
   2079                     inputManager.showSoftInput(mInput, 0);
   2080                 }
   2081             });
   2082 
   2083             return dialog;
   2084         }
   2085 
   2086         private void changeFolderName() {
   2087             final String name = mInput.getText().toString();
   2088             if (!TextUtils.isEmpty(name)) {
   2089                 // Make sure we have the right folder info
   2090                 mFolderInfo = sFolders.get(mFolderInfo.id);
   2091                 mFolderInfo.title = name;
   2092                 LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
   2093 
   2094                 if (mWorkspaceLoading) {
   2095                     lockAllApps();
   2096                     mModel.startLoader(Launcher.this, false);
   2097                 } else {
   2098                     final FolderIcon folderIcon = (FolderIcon)
   2099                             mWorkspace.getViewForTag(mFolderInfo);
   2100                     if (folderIcon != null) {
   2101                         // TODO: At some point we'll probably want some version of setting
   2102                         // the text for a folder icon.
   2103                         //folderIcon.setText(name);
   2104                         getWorkspace().requestLayout();
   2105                     } else {
   2106                         lockAllApps();
   2107                         mWorkspaceLoading = true;
   2108                         mModel.startLoader(Launcher.this, false);
   2109                     }
   2110                 }
   2111             }
   2112             cleanup();
   2113         }
   2114 
   2115         private void cleanup() {
   2116             dismissDialog(DIALOG_RENAME_FOLDER);
   2117             mWaitingForResult = false;
   2118             mFolderInfo = null;
   2119         }
   2120     }
   2121 
   2122     // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
   2123     public boolean isAllAppsVisible() {
   2124         return (mState == State.APPS_CUSTOMIZE);
   2125     }
   2126 
   2127     // AllAppsView.Watcher
   2128     public void zoomed(float zoom) {
   2129         if (zoom == 1.0f) {
   2130             mWorkspace.setVisibility(View.GONE);
   2131         }
   2132     }
   2133 
   2134     /**
   2135      * Helper method for the cameraZoomIn/cameraZoomOut animations
   2136      * @param view The view being animated
   2137      * @param state The state that we are moving in or out of (eg. APPS_CUSTOMIZE)
   2138      * @param scaleFactor The scale factor used for the zoom
   2139      */
   2140     private void setPivotsForZoom(View view, float scaleFactor) {
   2141         view.setPivotX(view.getWidth() / 2.0f);
   2142         view.setPivotY(view.getHeight() / 2.0f);
   2143     }
   2144 
   2145     void updateWallpaperVisibility(boolean visible) {
   2146         int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
   2147         int curflags = getWindow().getAttributes().flags
   2148                 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
   2149         if (wpflags != curflags) {
   2150             getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
   2151         }
   2152     }
   2153 
   2154     /**
   2155      * Things to test when changing the following seven functions.
   2156      *   - Home from workspace
   2157      *          - from center screen
   2158      *          - from other screens
   2159      *   - Home from all apps
   2160      *          - from center screen
   2161      *          - from other screens
   2162      *   - Back from all apps
   2163      *          - from center screen
   2164      *          - from other screens
   2165      *   - Launch app from workspace and quit
   2166      *          - with back
   2167      *          - with home
   2168      *   - Launch app from all apps and quit
   2169      *          - with back
   2170      *          - with home
   2171      *   - Go to a screen that's not the default, then all
   2172      *     apps, and launch and app, and go back
   2173      *          - with back
   2174      *          -with home
   2175      *   - On workspace, long press power and go back
   2176      *          - with back
   2177      *          - with home
   2178      *   - On all apps, long press power and go back
   2179      *          - with back
   2180      *          - with home
   2181      *   - On workspace, power off
   2182      *   - On all apps, power off
   2183      *   - Launch an app and turn off the screen while in that app
   2184      *          - Go back with home key
   2185      *          - Go back with back key  TODO: make this not go to workspace
   2186      *          - From all apps
   2187      *          - From workspace
   2188      *   - Enter and exit car mode (becuase it causes an extra configuration changed)
   2189      *          - From all apps
   2190      *          - From the center workspace
   2191      *          - From another workspace
   2192      */
   2193 
   2194     /**
   2195      * Zoom the camera out from the workspace to reveal 'toView'.
   2196      * Assumes that the view to show is anchored at either the very top or very bottom
   2197      * of the screen.
   2198      */
   2199     private void showAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
   2200         if (mStateAnimation != null) {
   2201             mStateAnimation.cancel();
   2202             mStateAnimation = null;
   2203         }
   2204         final Resources res = getResources();
   2205         final Launcher instance = this;
   2206 
   2207         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
   2208         final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
   2209         final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
   2210         final View toView = mAppsCustomizeTabHost;
   2211         final int startDelay =
   2212                 res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
   2213 
   2214         setPivotsForZoom(toView, scale);
   2215 
   2216         // Shrink workspaces away if going to AppsCustomize from workspace
   2217         mWorkspace.changeState(Workspace.State.SMALL, animated);
   2218 
   2219         if (animated) {
   2220             final ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
   2221             scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator());
   2222             scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
   2223                 public void onAnimationUpdate(float a, float b) {
   2224                     toView.setScaleX(a * scale + b * 1f);
   2225                     toView.setScaleY(a * scale + b * 1f);
   2226                 }
   2227             });
   2228 
   2229             toView.setVisibility(View.VISIBLE);
   2230             toView.setAlpha(0f);
   2231             ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(fadeDuration);
   2232             alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
   2233             alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
   2234                 public void onAnimationUpdate(float a, float b) {
   2235                     // don't need to invalidate because we do so above
   2236                     toView.setAlpha(a * 0f + b * 1f);
   2237                 }
   2238             });
   2239             alphaAnim.setStartDelay(startDelay);
   2240             alphaAnim.start();
   2241 
   2242             scaleAnim.addListener(new AnimatorListenerAdapter() {
   2243                 boolean animationCancelled = false;
   2244 
   2245                 @Override
   2246                 public void onAnimationStart(Animator animation) {
   2247                     updateWallpaperVisibility(true);
   2248                     // Prepare the position
   2249                     toView.setTranslationX(0.0f);
   2250                     toView.setTranslationY(0.0f);
   2251                     toView.setVisibility(View.VISIBLE);
   2252                     toView.bringToFront();
   2253                 }
   2254                 @Override
   2255                 public void onAnimationEnd(Animator animation) {
   2256                     // If we don't set the final scale values here, if this animation is cancelled
   2257                     // it will have the wrong scale value and subsequent cameraPan animations will
   2258                     // not fix that
   2259                     toView.setScaleX(1.0f);
   2260                     toView.setScaleY(1.0f);
   2261                     if (toView instanceof LauncherTransitionable) {
   2262                         ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance,
   2263                                 scaleAnim, false);
   2264                     }
   2265 
   2266                     if (!springLoaded && !LauncherApplication.isScreenLarge()) {
   2267                         // Hide the workspace scrollbar
   2268                         mWorkspace.hideScrollingIndicator(true);
   2269                         hideDockDivider();
   2270                     }
   2271                     if (!animationCancelled) {
   2272                         updateWallpaperVisibility(false);
   2273                     }
   2274                 }
   2275 
   2276                 @Override
   2277                 public void onAnimationCancel(Animator animation) {
   2278                     animationCancelled = true;
   2279                 }
   2280             });
   2281 
   2282             // toView should appear right at the end of the workspace shrink animation
   2283             mStateAnimation = new AnimatorSet();
   2284             mStateAnimation.play(scaleAnim).after(startDelay);
   2285 
   2286             boolean delayAnim = false;
   2287             if (toView instanceof LauncherTransitionable) {
   2288                 LauncherTransitionable lt = (LauncherTransitionable) toView;
   2289                 delayAnim = lt.onLauncherTransitionStart(instance, mStateAnimation, false);
   2290             }
   2291             // if the anim is delayed, the LauncherTransitionable is responsible for starting it
   2292             if (!delayAnim) {
   2293                 // TODO: q-- what if this anim is cancelled before being started? or started after
   2294                 // being cancelled?
   2295                 mStateAnimation.start();
   2296             }
   2297         } else {
   2298             toView.setTranslationX(0.0f);
   2299             toView.setTranslationY(0.0f);
   2300             toView.setScaleX(1.0f);
   2301             toView.setScaleY(1.0f);
   2302             toView.setVisibility(View.VISIBLE);
   2303             toView.bringToFront();
   2304             if (toView instanceof LauncherTransitionable) {
   2305                 ((LauncherTransitionable) toView).onLauncherTransitionStart(instance, null, false);
   2306                 ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance, null, false);
   2307 
   2308                 if (!springLoaded && !LauncherApplication.isScreenLarge()) {
   2309                     // Hide the workspace scrollbar
   2310                     mWorkspace.hideScrollingIndicator(true);
   2311                     hideDockDivider();
   2312                 }
   2313             }
   2314             updateWallpaperVisibility(false);
   2315         }
   2316     }
   2317 
   2318     /**
   2319      * Zoom the camera back into the workspace, hiding 'fromView'.
   2320      * This is the opposite of showAppsCustomizeHelper.
   2321      * @param animated If true, the transition will be animated.
   2322      */
   2323     private void hideAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
   2324         if (mStateAnimation != null) {
   2325             mStateAnimation.cancel();
   2326             mStateAnimation = null;
   2327         }
   2328         Resources res = getResources();
   2329         final Launcher instance = this;
   2330 
   2331         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
   2332         final float scaleFactor = (float)
   2333                 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
   2334         final View fromView = mAppsCustomizeTabHost;
   2335 
   2336         setPivotsForZoom(fromView, scaleFactor);
   2337         updateWallpaperVisibility(true);
   2338         showHotseat(animated);
   2339         if (animated) {
   2340             final float oldScaleX = fromView.getScaleX();
   2341             final float oldScaleY = fromView.getScaleY();
   2342 
   2343             ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
   2344             scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator());
   2345             scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
   2346                 public void onAnimationUpdate(float a, float b) {
   2347                     fromView.setScaleX(a * oldScaleX + b * scaleFactor);
   2348                     fromView.setScaleY(a * oldScaleY + b * scaleFactor);
   2349                 }
   2350             });
   2351             final ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f);
   2352             alphaAnim.setDuration(res.getInteger(R.integer.config_appsCustomizeFadeOutTime));
   2353             alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
   2354             alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
   2355                 public void onAnimationUpdate(float a, float b) {
   2356                     fromView.setAlpha(a * 1f + b * 0f);
   2357                 }
   2358             });
   2359             if (fromView instanceof LauncherTransitionable) {
   2360                 ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, alphaAnim,
   2361                         true);
   2362             }
   2363             alphaAnim.addListener(new AnimatorListenerAdapter() {
   2364                 @Override
   2365                 public void onAnimationEnd(Animator animation) {
   2366                     updateWallpaperVisibility(true);
   2367                     fromView.setVisibility(View.GONE);
   2368                     if (fromView instanceof LauncherTransitionable) {
   2369                         ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance,
   2370                                 alphaAnim, true);
   2371                     }
   2372                     mWorkspace.hideScrollingIndicator(false);
   2373                 }
   2374             });
   2375 
   2376             mStateAnimation = new AnimatorSet();
   2377             mStateAnimation.playTogether(scaleAnim, alphaAnim);
   2378             mStateAnimation.start();
   2379         } else {
   2380             fromView.setVisibility(View.GONE);
   2381             if (fromView instanceof LauncherTransitionable) {
   2382                 ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, null, true);
   2383                 ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance, null, true);
   2384             }
   2385             mWorkspace.hideScrollingIndicator(false);
   2386         }
   2387     }
   2388 
   2389     @Override
   2390     public void onTrimMemory(int level) {
   2391         super.onTrimMemory(level);
   2392         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
   2393             mAppsCustomizeTabHost.onTrimMemory();
   2394         }
   2395     }
   2396 
   2397     void showWorkspace(boolean animated) {
   2398         Resources res = getResources();
   2399         int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
   2400 
   2401         mWorkspace.changeState(Workspace.State.NORMAL, animated, stagger);
   2402         if (mState != State.WORKSPACE) {
   2403             mWorkspace.setVisibility(View.VISIBLE);
   2404             hideAppsCustomizeHelper(animated, false);
   2405 
   2406             // Show the search bar and hotseat
   2407             mSearchDropTargetBar.showSearchBar(animated);
   2408             // We only need to animate in the dock divider if we're going from spring loaded mode
   2409             showDockDivider(animated && mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
   2410 
   2411             // Set focus to the AppsCustomize button
   2412             if (mAllAppsButton != null) {
   2413                 mAllAppsButton.requestFocus();
   2414             }
   2415         }
   2416 
   2417         mWorkspace.flashScrollingIndicator(animated);
   2418 
   2419         // Change the state *after* we've called all the transition code
   2420         mState = State.WORKSPACE;
   2421 
   2422         // Resume the auto-advance of widgets
   2423         mUserPresent = true;
   2424         updateRunning();
   2425 
   2426         // send an accessibility event to announce the context change
   2427         getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   2428     }
   2429 
   2430     void showAllApps(boolean animated) {
   2431         if (mState != State.WORKSPACE) return;
   2432 
   2433         showAppsCustomizeHelper(animated, false);
   2434         mAppsCustomizeTabHost.requestFocus();
   2435 
   2436         // Hide the search bar and hotseat
   2437         mSearchDropTargetBar.hideSearchBar(animated);
   2438 
   2439         // Change the state *after* we've called all the transition code
   2440         mState = State.APPS_CUSTOMIZE;
   2441 
   2442         // Pause the auto-advance of widgets until we are out of AllApps
   2443         mUserPresent = false;
   2444         updateRunning();
   2445         closeFolder();
   2446 
   2447         // Send an accessibility event to announce the context change
   2448         getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   2449     }
   2450 
   2451     void enterSpringLoadedDragMode() {
   2452         if (mState == State.APPS_CUSTOMIZE) {
   2453             mWorkspace.changeState(Workspace.State.SPRING_LOADED);
   2454             hideAppsCustomizeHelper(true, true);
   2455             hideDockDivider();
   2456             mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
   2457         }
   2458     }
   2459 
   2460     void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay) {
   2461         if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
   2462 
   2463         mHandler.postDelayed(new Runnable() {
   2464             @Override
   2465             public void run() {
   2466                 if (successfulDrop) {
   2467                     // Before we show workspace, hide all apps again because
   2468                     // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
   2469                     // clean up our state transition functions
   2470                     mAppsCustomizeTabHost.setVisibility(View.GONE);
   2471                     mSearchDropTargetBar.showSearchBar(true);
   2472                     showWorkspace(true);
   2473                 } else {
   2474                     exitSpringLoadedDragMode();
   2475                 }
   2476             }
   2477         }, (extendedDelay ?
   2478                 EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
   2479                 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
   2480     }
   2481 
   2482     void exitSpringLoadedDragMode() {
   2483         if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
   2484             final boolean animated = true;
   2485             final boolean springLoaded = true;
   2486             showAppsCustomizeHelper(animated, springLoaded);
   2487             mState = State.APPS_CUSTOMIZE;
   2488         }
   2489         // Otherwise, we are not in spring loaded mode, so don't do anything.
   2490     }
   2491 
   2492     void hideDockDivider() {
   2493         if (mQsbDivider != null && mDockDivider != null) {
   2494             mQsbDivider.setVisibility(View.INVISIBLE);
   2495             mDockDivider.setVisibility(View.INVISIBLE);
   2496         }
   2497     }
   2498 
   2499     void showDockDivider(boolean animated) {
   2500         if (mQsbDivider != null && mDockDivider != null) {
   2501             mQsbDivider.setVisibility(View.VISIBLE);
   2502             mDockDivider.setVisibility(View.VISIBLE);
   2503             if (mDividerAnimator != null) {
   2504                 mDividerAnimator.cancel();
   2505                 mQsbDivider.setAlpha(1f);
   2506                 mDockDivider.setAlpha(1f);
   2507                 mDividerAnimator = null;
   2508             }
   2509             if (animated) {
   2510                 mDividerAnimator = new AnimatorSet();
   2511                 mDividerAnimator.playTogether(ObjectAnimator.ofFloat(mQsbDivider, "alpha", 1f),
   2512                         ObjectAnimator.ofFloat(mDockDivider, "alpha", 1f));
   2513                 mDividerAnimator.setDuration(mSearchDropTargetBar.getTransitionInDuration());
   2514                 mDividerAnimator.start();
   2515             }
   2516         }
   2517     }
   2518 
   2519     void lockAllApps() {
   2520         // TODO
   2521     }
   2522 
   2523     void unlockAllApps() {
   2524         // TODO
   2525     }
   2526 
   2527     public boolean isAllAppsCustomizeOpen() {
   2528         return mState == State.APPS_CUSTOMIZE;
   2529     }
   2530 
   2531     /**
   2532      * Shows the hotseat area.
   2533      */
   2534     void showHotseat(boolean animated) {
   2535         if (!LauncherApplication.isScreenLarge()) {
   2536             if (animated) {
   2537                 int duration = mSearchDropTargetBar.getTransitionInDuration();
   2538                 mHotseat.animate().alpha(1f).setDuration(duration);
   2539             } else {
   2540                 mHotseat.setAlpha(1f);
   2541             }
   2542         }
   2543     }
   2544 
   2545     /**
   2546      * Hides the hotseat area.
   2547      */
   2548     void hideHotseat(boolean animated) {
   2549         if (!LauncherApplication.isScreenLarge()) {
   2550             if (animated) {
   2551                 int duration = mSearchDropTargetBar.getTransitionOutDuration();
   2552                 mHotseat.animate().alpha(0f).setDuration(duration);
   2553             } else {
   2554                 mHotseat.setAlpha(0f);
   2555             }
   2556         }
   2557     }
   2558 
   2559     /**
   2560      * Add an item from all apps or customize onto the given workspace screen.
   2561      * If layout is null, add to the current screen.
   2562      */
   2563     void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
   2564         if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
   2565             showOutOfSpaceMessage();
   2566         }
   2567     }
   2568 
   2569     /** Maps the current orientation to an index for referencing orientation correct global icons */
   2570     private int getCurrentOrientationIndexForGlobalIcons() {
   2571         // default - 0, landscape - 1
   2572         switch (getResources().getConfiguration().orientation) {
   2573         case Configuration.ORIENTATION_LANDSCAPE:
   2574             return 1;
   2575         default:
   2576             return 0;
   2577         }
   2578     }
   2579 
   2580     private Drawable getExternalPackageToolbarIcon(ComponentName activityName) {
   2581         try {
   2582             PackageManager packageManager = getPackageManager();
   2583             // Look for the toolbar icon specified in the activity meta-data
   2584             Bundle metaData = packageManager.getActivityInfo(
   2585                     activityName, PackageManager.GET_META_DATA).metaData;
   2586             if (metaData != null) {
   2587                 int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
   2588                 if (iconResId != 0) {
   2589                     Resources res = packageManager.getResourcesForActivity(activityName);
   2590                     return res.getDrawable(iconResId);
   2591                 }
   2592             }
   2593         } catch (NameNotFoundException e) {
   2594             // This can happen if the activity defines an invalid drawable
   2595             Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
   2596                     " not found", e);
   2597         } catch (Resources.NotFoundException nfe) {
   2598             // This can happen if the activity defines an invalid drawable
   2599             Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
   2600                     nfe);
   2601         }
   2602         return null;
   2603     }
   2604 
   2605     // if successful in getting icon, return it; otherwise, set button to use default drawable
   2606     private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
   2607             int buttonId, ComponentName activityName, int fallbackDrawableId) {
   2608         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
   2609         Resources r = getResources();
   2610         int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
   2611         int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
   2612 
   2613         TextView button = (TextView) findViewById(buttonId);
   2614         // If we were unable to find the icon via the meta-data, use a generic one
   2615         if (toolbarIcon == null) {
   2616             toolbarIcon = r.getDrawable(fallbackDrawableId);
   2617             toolbarIcon.setBounds(0, 0, w, h);
   2618             if (button != null) {
   2619                 button.setCompoundDrawables(toolbarIcon, null, null, null);
   2620             }
   2621             return null;
   2622         } else {
   2623             toolbarIcon.setBounds(0, 0, w, h);
   2624             if (button != null) {
   2625                 button.setCompoundDrawables(toolbarIcon, null, null, null);
   2626             }
   2627             return toolbarIcon.getConstantState();
   2628         }
   2629     }
   2630 
   2631     // if successful in getting icon, return it; otherwise, set button to use default drawable
   2632     private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
   2633             int buttonId, ComponentName activityName, int fallbackDrawableId) {
   2634         ImageView button = (ImageView) findViewById(buttonId);
   2635         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
   2636 
   2637         if (button != null) {
   2638             // If we were unable to find the icon via the meta-data, use a
   2639             // generic one
   2640             if (toolbarIcon == null) {
   2641                 button.setImageResource(fallbackDrawableId);
   2642             } else {
   2643                 button.setImageDrawable(toolbarIcon);
   2644             }
   2645         }
   2646 
   2647         return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
   2648 
   2649     }
   2650 
   2651     private void updateTextButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
   2652         TextView button = (TextView) findViewById(buttonId);
   2653         button.setCompoundDrawables(d.newDrawable(getResources()), null, null, null);
   2654     }
   2655 
   2656     private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
   2657         ImageView button = (ImageView) findViewById(buttonId);
   2658         button.setImageDrawable(d.newDrawable(getResources()));
   2659     }
   2660 
   2661     private void invalidatePressedFocusedStates(View container, View button) {
   2662         if (container instanceof HolographicLinearLayout) {
   2663             HolographicLinearLayout layout = (HolographicLinearLayout) container;
   2664             layout.invalidatePressedFocusedStates();
   2665         } else if (button instanceof HolographicImageView) {
   2666             HolographicImageView view = (HolographicImageView) button;
   2667             view.invalidatePressedFocusedStates();
   2668         }
   2669     }
   2670 
   2671     private boolean updateGlobalSearchIcon() {
   2672         final View searchButtonContainer = findViewById(R.id.search_button_container);
   2673         final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
   2674         final View searchDivider = findViewById(R.id.search_divider);
   2675         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
   2676         final View voiceButton = findViewById(R.id.voice_button);
   2677 
   2678         final SearchManager searchManager =
   2679                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
   2680         ComponentName activityName = searchManager.getGlobalSearchActivity();
   2681         if (activityName != null) {
   2682             int coi = getCurrentOrientationIndexForGlobalIcons();
   2683             sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
   2684                     R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo);
   2685             if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
   2686             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
   2687             searchButton.setVisibility(View.VISIBLE);
   2688             invalidatePressedFocusedStates(searchButtonContainer, searchButton);
   2689             return true;
   2690         } else {
   2691             // We disable both search and voice search when there is no global search provider
   2692             if (searchDivider != null) searchDivider.setVisibility(View.GONE);
   2693             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
   2694             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
   2695             searchButton.setVisibility(View.GONE);
   2696             voiceButton.setVisibility(View.GONE);
   2697             return false;
   2698         }
   2699     }
   2700 
   2701     private void updateGlobalSearchIcon(Drawable.ConstantState d) {
   2702         final View searchButtonContainer = findViewById(R.id.search_button_container);
   2703         final View searchButton = (ImageView) findViewById(R.id.search_button);
   2704         updateButtonWithDrawable(R.id.search_button, d);
   2705         invalidatePressedFocusedStates(searchButtonContainer, searchButton);
   2706     }
   2707 
   2708     private boolean updateVoiceSearchIcon(boolean searchVisible) {
   2709         final View searchDivider = findViewById(R.id.search_divider);
   2710         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
   2711         final View voiceButton = findViewById(R.id.voice_button);
   2712 
   2713         // We only show/update the voice search icon if the search icon is enabled as well
   2714         Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
   2715         ComponentName activityName = intent.resolveActivity(getPackageManager());
   2716         if (searchVisible && activityName != null) {
   2717             int coi = getCurrentOrientationIndexForGlobalIcons();
   2718             sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
   2719                     R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo);
   2720             if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
   2721             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
   2722             voiceButton.setVisibility(View.VISIBLE);
   2723             invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
   2724             return true;
   2725         } else {
   2726             if (searchDivider != null) searchDivider.setVisibility(View.GONE);
   2727             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
   2728             voiceButton.setVisibility(View.GONE);
   2729             return false;
   2730         }
   2731     }
   2732 
   2733     private void updateVoiceSearchIcon(Drawable.ConstantState d) {
   2734         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
   2735         final View voiceButton = findViewById(R.id.voice_button);
   2736         updateButtonWithDrawable(R.id.voice_button, d);
   2737         invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
   2738     }
   2739 
   2740     /**
   2741      * Sets the app market icon
   2742      */
   2743     private void updateAppMarketIcon() {
   2744         final View marketButton = findViewById(R.id.market_button);
   2745         Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
   2746         // Find the app market activity by resolving an intent.
   2747         // (If multiple app markets are installed, it will return the ResolverActivity.)
   2748         ComponentName activityName = intent.resolveActivity(getPackageManager());
   2749         if (activityName != null) {
   2750             int coi = getCurrentOrientationIndexForGlobalIcons();
   2751             mAppMarketIntent = intent;
   2752             sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
   2753                     R.id.market_button, activityName, R.drawable.ic_launcher_market_holo);
   2754             marketButton.setVisibility(View.VISIBLE);
   2755         } else {
   2756             // We should hide and disable the view so that we don't try and restore the visibility
   2757             // of it when we swap between drag & normal states from IconDropTarget subclasses.
   2758             marketButton.setVisibility(View.GONE);
   2759             marketButton.setEnabled(false);
   2760         }
   2761     }
   2762 
   2763     private void updateAppMarketIcon(Drawable.ConstantState d) {
   2764         updateTextButtonWithDrawable(R.id.market_button, d);
   2765     }
   2766 
   2767     /**
   2768      * Displays the shortcut creation dialog and launches, if necessary, the
   2769      * appropriate activity.
   2770      */
   2771     private class CreateShortcut implements DialogInterface.OnClickListener,
   2772             DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
   2773             DialogInterface.OnShowListener {
   2774 
   2775         private AddAdapter mAdapter;
   2776 
   2777         Dialog createDialog() {
   2778             mAdapter = new AddAdapter(Launcher.this);
   2779 
   2780             final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this,
   2781                     AlertDialog.THEME_HOLO_DARK);
   2782             builder.setAdapter(mAdapter, this);
   2783 
   2784             AlertDialog dialog = builder.create();
   2785             dialog.setOnCancelListener(this);
   2786             dialog.setOnDismissListener(this);
   2787             dialog.setOnShowListener(this);
   2788 
   2789             return dialog;
   2790         }
   2791 
   2792         public void onCancel(DialogInterface dialog) {
   2793             mWaitingForResult = false;
   2794             cleanup();
   2795         }
   2796 
   2797         public void onDismiss(DialogInterface dialog) {
   2798             mWaitingForResult = false;
   2799             cleanup();
   2800         }
   2801 
   2802         private void cleanup() {
   2803             try {
   2804                 dismissDialog(DIALOG_CREATE_SHORTCUT);
   2805             } catch (Exception e) {
   2806                 // An exception is thrown if the dialog is not visible, which is fine
   2807             }
   2808         }
   2809 
   2810         /**
   2811          * Handle the action clicked in the "Add to home" dialog.
   2812          */
   2813         public void onClick(DialogInterface dialog, int which) {
   2814             cleanup();
   2815 
   2816             AddAdapter.ListItem item = (AddAdapter.ListItem) mAdapter.getItem(which);
   2817             switch (item.actionTag) {
   2818                 case AddAdapter.ITEM_APPLICATION: {
   2819                     if (mAppsCustomizeTabHost != null) {
   2820                         mAppsCustomizeTabHost.selectAppsTab();
   2821                     }
   2822                     showAllApps(true);
   2823                     break;
   2824                 }
   2825                 case AddAdapter.ITEM_APPWIDGET: {
   2826                     if (mAppsCustomizeTabHost != null) {
   2827                         mAppsCustomizeTabHost.selectWidgetsTab();
   2828                     }
   2829                     showAllApps(true);
   2830                     break;
   2831                 }
   2832                 case AddAdapter.ITEM_WALLPAPER: {
   2833                     startWallpaper();
   2834                     break;
   2835                 }
   2836             }
   2837         }
   2838 
   2839         public void onShow(DialogInterface dialog) {
   2840             mWaitingForResult = true;
   2841         }
   2842     }
   2843 
   2844     /**
   2845      * Receives notifications when system dialogs are to be closed.
   2846      */
   2847     private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
   2848         @Override
   2849         public void onReceive(Context context, Intent intent) {
   2850             closeSystemDialogs();
   2851         }
   2852     }
   2853 
   2854     /**
   2855      * Receives notifications whenever the appwidgets are reset.
   2856      */
   2857     private class AppWidgetResetObserver extends ContentObserver {
   2858         public AppWidgetResetObserver() {
   2859             super(new Handler());
   2860         }
   2861 
   2862         @Override
   2863         public void onChange(boolean selfChange) {
   2864             onAppWidgetReset();
   2865         }
   2866     }
   2867 
   2868     /**
   2869      * If the activity is currently paused, signal that we need to re-run the loader
   2870      * in onResume.
   2871      *
   2872      * This needs to be called from incoming places where resources might have been loaded
   2873      * while we are paused.  That is becaues the Configuration might be wrong
   2874      * when we're not running, and if it comes back to what it was when we
   2875      * were paused, we are not restarted.
   2876      *
   2877      * Implementation of the method from LauncherModel.Callbacks.
   2878      *
   2879      * @return true if we are currently paused.  The caller might be able to
   2880      * skip some work in that case since we will come back again.
   2881      */
   2882     public boolean setLoadOnResume() {
   2883         if (mPaused) {
   2884             Log.i(TAG, "setLoadOnResume");
   2885             mOnResumeNeedsLoad = true;
   2886             return true;
   2887         } else {
   2888             return false;
   2889         }
   2890     }
   2891 
   2892     /**
   2893      * Implementation of the method from LauncherModel.Callbacks.
   2894      */
   2895     public int getCurrentWorkspaceScreen() {
   2896         if (mWorkspace != null) {
   2897             return mWorkspace.getCurrentPage();
   2898         } else {
   2899             return SCREEN_COUNT / 2;
   2900         }
   2901     }
   2902 
   2903 
   2904     /**
   2905      * Refreshes the shortcuts shown on the workspace.
   2906      *
   2907      * Implementation of the method from LauncherModel.Callbacks.
   2908      */
   2909     public void startBinding() {
   2910         final Workspace workspace = mWorkspace;
   2911 
   2912         mWorkspace.clearDropTargets();
   2913         int count = workspace.getChildCount();
   2914         for (int i = 0; i < count; i++) {
   2915             // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
   2916             final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
   2917             layoutParent.removeAllViewsInLayout();
   2918         }
   2919         mWidgetsToAdvance.clear();
   2920         if (mHotseat != null) {
   2921             mHotseat.resetLayout();
   2922         }
   2923     }
   2924 
   2925     /**
   2926      * Bind the items start-end from the list.
   2927      *
   2928      * Implementation of the method from LauncherModel.Callbacks.
   2929      */
   2930     public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
   2931         setLoadOnResume();
   2932 
   2933         final Workspace workspace = mWorkspace;
   2934         for (int i=start; i<end; i++) {
   2935             final ItemInfo item = shortcuts.get(i);
   2936 
   2937             // Short circuit if we are loading dock items for a configuration which has no dock
   2938             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
   2939                     mHotseat == null) {
   2940                 continue;
   2941             }
   2942 
   2943             switch (item.itemType) {
   2944                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
   2945                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
   2946                     View shortcut = createShortcut((ShortcutInfo)item);
   2947                     workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
   2948                             item.cellY, 1, 1, false);
   2949                     break;
   2950                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
   2951                     FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
   2952                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
   2953                             (FolderInfo) item, mIconCache);
   2954                     workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
   2955                             item.cellY, 1, 1, false);
   2956                     break;
   2957             }
   2958         }
   2959         workspace.requestLayout();
   2960     }
   2961 
   2962     /**
   2963      * Implementation of the method from LauncherModel.Callbacks.
   2964      */
   2965     public void bindFolders(HashMap<Long, FolderInfo> folders) {
   2966         setLoadOnResume();
   2967         sFolders.clear();
   2968         sFolders.putAll(folders);
   2969     }
   2970 
   2971     /**
   2972      * Add the views for a widget to the workspace.
   2973      *
   2974      * Implementation of the method from LauncherModel.Callbacks.
   2975      */
   2976     public void bindAppWidget(LauncherAppWidgetInfo item) {
   2977         setLoadOnResume();
   2978 
   2979         final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
   2980         if (DEBUG_WIDGETS) {
   2981             Log.d(TAG, "bindAppWidget: " + item);
   2982         }
   2983         final Workspace workspace = mWorkspace;
   2984 
   2985         final int appWidgetId = item.appWidgetId;
   2986         final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
   2987         if (DEBUG_WIDGETS) {
   2988             Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
   2989         }
   2990 
   2991         item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
   2992 
   2993         item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
   2994         item.hostView.setTag(item);
   2995 
   2996         workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
   2997                 item.cellY, item.spanX, item.spanY, false);
   2998 
   2999         addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
   3000 
   3001         workspace.requestLayout();
   3002 
   3003         if (DEBUG_WIDGETS) {
   3004             Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
   3005                     + (SystemClock.uptimeMillis()-start) + "ms");
   3006         }
   3007     }
   3008 
   3009     /**
   3010      * Callback saying that there aren't any more items to bind.
   3011      *
   3012      * Implementation of the method from LauncherModel.Callbacks.
   3013      */
   3014     public void finishBindingItems() {
   3015         setLoadOnResume();
   3016 
   3017         if (mSavedState != null) {
   3018             if (!mWorkspace.hasFocus()) {
   3019                 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
   3020             }
   3021             mSavedState = null;
   3022         }
   3023 
   3024         if (mSavedInstanceState != null) {
   3025             super.onRestoreInstanceState(mSavedInstanceState);
   3026             mSavedInstanceState = null;
   3027         }
   3028 
   3029         mWorkspaceLoading = false;
   3030 
   3031         // If we received the result of any pending adds while the loader was running (e.g. the
   3032         // widget configuration forced an orientation change), process them now.
   3033         for (int i = 0; i < sPendingAddList.size(); i++) {
   3034             completeAdd(sPendingAddList.get(i));
   3035         }
   3036         sPendingAddList.clear();
   3037 
   3038         // Update the market app icon as necessary (the other icons will be managed in response to
   3039         // package changes in bindSearchablesChanged()
   3040         updateAppMarketIcon();
   3041 
   3042         mWorkspace.post(mBuildLayersRunnable);
   3043     }
   3044 
   3045     @Override
   3046     public void bindSearchablesChanged() {
   3047         boolean searchVisible = updateGlobalSearchIcon();
   3048         boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
   3049         mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
   3050     }
   3051 
   3052     /**
   3053      * Add the icons for all apps.
   3054      *
   3055      * Implementation of the method from LauncherModel.Callbacks.
   3056      */
   3057     public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
   3058         // Remove the progress bar entirely; we could also make it GONE
   3059         // but better to remove it since we know it's not going to be used
   3060         View progressBar = mAppsCustomizeTabHost.
   3061             findViewById(R.id.apps_customize_progress_bar);
   3062         if (progressBar != null) {
   3063             ((ViewGroup)progressBar.getParent()).removeView(progressBar);
   3064         }
   3065         // We just post the call to setApps so the user sees the progress bar
   3066         // disappear-- otherwise, it just looks like the progress bar froze
   3067         // which doesn't look great
   3068         mAppsCustomizeTabHost.post(new Runnable() {
   3069             public void run() {
   3070                 if (mAppsCustomizeContent != null) {
   3071                     mAppsCustomizeContent.setApps(apps);
   3072                 }
   3073             }
   3074         });
   3075     }
   3076 
   3077     /**
   3078      * A package was installed.
   3079      *
   3080      * Implementation of the method from LauncherModel.Callbacks.
   3081      */
   3082     public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
   3083         setLoadOnResume();
   3084         removeDialog(DIALOG_CREATE_SHORTCUT);
   3085 
   3086         if (mAppsCustomizeContent != null) {
   3087             mAppsCustomizeContent.addApps(apps);
   3088         }
   3089     }
   3090 
   3091     /**
   3092      * A package was updated.
   3093      *
   3094      * Implementation of the method from LauncherModel.Callbacks.
   3095      */
   3096     public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
   3097         setLoadOnResume();
   3098         removeDialog(DIALOG_CREATE_SHORTCUT);
   3099         if (mWorkspace != null) {
   3100             mWorkspace.updateShortcuts(apps);
   3101         }
   3102 
   3103         if (mAppsCustomizeContent != null) {
   3104             mAppsCustomizeContent.updateApps(apps);
   3105         }
   3106     }
   3107 
   3108     /**
   3109      * A package was uninstalled.
   3110      *
   3111      * Implementation of the method from LauncherModel.Callbacks.
   3112      */
   3113     public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) {
   3114         removeDialog(DIALOG_CREATE_SHORTCUT);
   3115         if (permanent) {
   3116             mWorkspace.removeItems(apps);
   3117         }
   3118 
   3119         if (mAppsCustomizeContent != null) {
   3120             mAppsCustomizeContent.removeApps(apps);
   3121         }
   3122 
   3123         // Notify the drag controller
   3124         mDragController.onAppsRemoved(apps, this);
   3125     }
   3126 
   3127     /**
   3128      * A number of packages were updated.
   3129      */
   3130     public void bindPackagesUpdated() {
   3131         if (mAppsCustomizeContent != null) {
   3132             mAppsCustomizeContent.onPackagesUpdated();
   3133         }
   3134     }
   3135 
   3136     private int mapConfigurationOriActivityInfoOri(int configOri) {
   3137         final Display d = getWindowManager().getDefaultDisplay();
   3138         int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
   3139         switch (d.getRotation()) {
   3140         case Surface.ROTATION_0:
   3141         case Surface.ROTATION_180:
   3142             // We are currently in the same basic orientation as the natural orientation
   3143             naturalOri = configOri;
   3144             break;
   3145         case Surface.ROTATION_90:
   3146         case Surface.ROTATION_270:
   3147             // We are currently in the other basic orientation to the natural orientation
   3148             naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
   3149                     Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
   3150             break;
   3151         }
   3152 
   3153         int[] oriMap = {
   3154                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
   3155                 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
   3156                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
   3157                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
   3158         };
   3159         // Since the map starts at portrait, we need to offset if this device's natural orientation
   3160         // is landscape.
   3161         int indexOffset = 0;
   3162         if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
   3163             indexOffset = 1;
   3164         }
   3165         return oriMap[(d.getRotation() + indexOffset) % 4];
   3166     }
   3167 
   3168     public void lockScreenOrientationOnLargeUI() {
   3169         if (LauncherApplication.isScreenLarge()) {
   3170             setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
   3171                     .getConfiguration().orientation));
   3172         }
   3173     }
   3174     public void unlockScreenOrientationOnLargeUI() {
   3175         if (LauncherApplication.isScreenLarge()) {
   3176             mHandler.postDelayed(new Runnable() {
   3177                 public void run() {
   3178                     setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
   3179                 }
   3180             }, mRestoreScreenOrientationDelay);
   3181         }
   3182     }
   3183 
   3184     /* Cling related */
   3185     private static final String PREFS_KEY = "com.android.launcher2.prefs";
   3186     private boolean isClingsEnabled() {
   3187         // disable clings when running in a test harness
   3188         if(ActivityManager.isRunningInTestHarness()) return false;
   3189 
   3190         return true;
   3191     }
   3192     private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) {
   3193         Cling cling = (Cling) findViewById(clingId);
   3194         if (cling != null) {
   3195             cling.init(this, positionData);
   3196             cling.setVisibility(View.VISIBLE);
   3197             cling.setLayerType(View.LAYER_TYPE_HARDWARE, null);
   3198             if (animate) {
   3199                 cling.buildLayer();
   3200                 cling.setAlpha(0f);
   3201                 cling.animate()
   3202                     .alpha(1f)
   3203                     .setInterpolator(new AccelerateInterpolator())
   3204                     .setDuration(SHOW_CLING_DURATION)
   3205                     .setStartDelay(delay)
   3206                     .start();
   3207             } else {
   3208                 cling.setAlpha(1f);
   3209             }
   3210         }
   3211         return cling;
   3212     }
   3213     private void dismissCling(final Cling cling, final String flag, int duration) {
   3214         if (cling != null) {
   3215             ObjectAnimator anim = ObjectAnimator.ofFloat(cling, "alpha", 0f);
   3216             anim.setDuration(duration);
   3217             anim.addListener(new AnimatorListenerAdapter() {
   3218                 public void onAnimationEnd(Animator animation) {
   3219                     cling.setVisibility(View.GONE);
   3220                     cling.cleanup();
   3221                     SharedPreferences prefs =
   3222                         getSharedPreferences("com.android.launcher2.prefs", Context.MODE_PRIVATE);
   3223                     SharedPreferences.Editor editor = prefs.edit();
   3224                     editor.putBoolean(flag, true);
   3225                     editor.commit();
   3226                 };
   3227             });
   3228             anim.start();
   3229         }
   3230     }
   3231     private void removeCling(int id) {
   3232         final View cling = findViewById(id);
   3233         if (cling != null) {
   3234             final ViewGroup parent = (ViewGroup) cling.getParent();
   3235             parent.post(new Runnable() {
   3236                 @Override
   3237                 public void run() {
   3238                     parent.removeView(cling);
   3239                 }
   3240             });
   3241         }
   3242     }
   3243     public void showFirstRunWorkspaceCling() {
   3244         // Enable the clings only if they have not been dismissed before
   3245         SharedPreferences prefs =
   3246             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
   3247         if (isClingsEnabled() && !prefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
   3248             initCling(R.id.workspace_cling, null, false, 0);
   3249         } else {
   3250             removeCling(R.id.workspace_cling);
   3251         }
   3252     }
   3253     public void showFirstRunAllAppsCling(int[] position) {
   3254         // Enable the clings only if they have not been dismissed before
   3255         SharedPreferences prefs =
   3256             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
   3257         if (isClingsEnabled() && !prefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
   3258             initCling(R.id.all_apps_cling, position, true, 0);
   3259         } else {
   3260             removeCling(R.id.all_apps_cling);
   3261         }
   3262     }
   3263     public Cling showFirstRunFoldersCling() {
   3264         // Enable the clings only if they have not been dismissed before
   3265         SharedPreferences prefs =
   3266             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
   3267         Cling cling = null;
   3268         if (isClingsEnabled() && !prefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
   3269             cling = initCling(R.id.folder_cling, null, true, 0);
   3270         } else {
   3271             removeCling(R.id.folder_cling);
   3272         }
   3273         return cling;
   3274     }
   3275     public boolean isFolderClingVisible() {
   3276         Cling cling = (Cling) findViewById(R.id.folder_cling);
   3277         if (cling != null) {
   3278             return cling.getVisibility() == View.VISIBLE;
   3279         }
   3280         return false;
   3281     }
   3282     public void dismissWorkspaceCling(View v) {
   3283         Cling cling = (Cling) findViewById(R.id.workspace_cling);
   3284         dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
   3285     }
   3286     public void dismissAllAppsCling(View v) {
   3287         Cling cling = (Cling) findViewById(R.id.all_apps_cling);
   3288         dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
   3289     }
   3290     public void dismissFolderCling(View v) {
   3291         Cling cling = (Cling) findViewById(R.id.folder_cling);
   3292         dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
   3293     }
   3294 
   3295     /**
   3296      * Prints out out state for debugging.
   3297      */
   3298     public void dumpState() {
   3299         Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
   3300         Log.d(TAG, "mSavedState=" + mSavedState);
   3301         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
   3302         Log.d(TAG, "mRestoring=" + mRestoring);
   3303         Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
   3304         Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
   3305         Log.d(TAG, "sFolders.size=" + sFolders.size());
   3306         mModel.dumpState();
   3307 
   3308         if (mAppsCustomizeContent != null) {
   3309             mAppsCustomizeContent.dumpState();
   3310         }
   3311         Log.d(TAG, "END launcher2 dump state");
   3312     }
   3313 
   3314     @Override
   3315     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
   3316         super.dump(prefix, fd, writer, args);
   3317         writer.println(" ");
   3318         writer.println("Debug logs: ");
   3319         for (int i = 0; i < sDumpLogs.size(); i++) {
   3320             writer.println("  " + sDumpLogs.get(i));
   3321         }
   3322     }
   3323 }
   3324 
   3325 interface LauncherTransitionable {
   3326     // return true if the callee will take care of start the animation by itself
   3327     boolean onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace);
   3328     void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace);
   3329 }
   3330