summaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/trebuchet/Launcher.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/cyanogenmod/trebuchet/Launcher.java')
-rw-r--r--src/com/cyanogenmod/trebuchet/Launcher.java3375
1 files changed, 3375 insertions, 0 deletions
diff --git a/src/com/cyanogenmod/trebuchet/Launcher.java b/src/com/cyanogenmod/trebuchet/Launcher.java
new file mode 100644
index 000000000..c713a6cbc
--- /dev/null
+++ b/src/com/cyanogenmod/trebuchet/Launcher.java
@@ -0,0 +1,3375 @@
+
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.trebuchet;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.SearchManager;
+import android.app.StatusBarManager;
+import android.app.UiModeManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.TextKeyListener;
+import android.util.Log;
+import android.view.Display;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Advanceable;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.common.Search;
+import com.cyanogenmod.trebuchet.R;
+import com.cyanogenmod.trebuchet.DropTarget.DragObject;
+import com.cyanogenmod.trebuchet.preference.*;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Default launcher application.
+ */
+public final class Launcher extends Activity
+ implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
+ View.OnTouchListener {
+ static final String TAG = "Launcher";
+ static final boolean LOGD = false;
+
+ static final boolean PROFILE_STARTUP = false;
+ static final boolean DEBUG_WIDGETS = false;
+
+ private static final int MENU_GROUP_WALLPAPER = 1;
+ private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
+ private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1;
+ private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1;
+ private static final int MENU_PREFERENCES = MENU_SYSTEM_SETTINGS + 1;
+ private static final int MENU_HELP = MENU_PREFERENCES + 1;
+
+ private static final int REQUEST_CREATE_SHORTCUT = 1;
+ private static final int REQUEST_CREATE_APPWIDGET = 5;
+ private static final int REQUEST_PICK_APPLICATION = 6;
+ private static final int REQUEST_PICK_SHORTCUT = 7;
+ private static final int REQUEST_PICK_APPWIDGET = 9;
+ private static final int REQUEST_PICK_WALLPAPER = 10;
+
+ static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
+
+ static final int SCREEN_COUNT = 5;
+ static final int DEFAULT_SCREEN = 2;
+
+ static final int DIALOG_CREATE_SHORTCUT = 1;
+ static final int DIALOG_RENAME_FOLDER = 2;
+
+ private static final String PREFERENCES = "launcher.preferences";
+
+ // Type: int
+ private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
+ // Type: int
+ private static final String RUNTIME_STATE = "launcher.state";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
+ // Type: boolean
+ private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
+ // Type: long
+ private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+
+ private static final String TOOLBAR_ICON_METADATA_NAME = "com.cyanogenmod.trebuchet.toolbar_icon";
+
+ /** The different states that Launcher can be in. */
+ private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
+ private State mState = State.WORKSPACE;
+ private AnimatorSet mStateAnimation;
+ private AnimatorSet mDividerAnimator;
+
+ static final int APPWIDGET_HOST_ID = 1024;
+ private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+ private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
+ private static final int SHOW_CLING_DURATION = 550;
+ private static final int DISMISS_CLING_DURATION = 250;
+
+ private static final Object sLock = new Object();
+ private static int sScreen = DEFAULT_SCREEN;
+
+ private final BroadcastReceiver mCloseSystemDialogsReceiver
+ = new CloseSystemDialogsIntentReceiver();
+ private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
+
+ private LayoutInflater mInflater;
+
+ private Workspace mWorkspace;
+ private View mQsbDivider;
+ private View mDockDivider;
+ private DragLayer mDragLayer;
+ private DragController mDragController;
+
+ private AppWidgetManager mAppWidgetManager;
+ private LauncherAppWidgetHost mAppWidgetHost;
+
+ private ItemInfo mPendingAddInfo = new ItemInfo();
+ private int[] mTmpAddItemCellCoordinates = new int[2];
+
+ private FolderInfo mFolderInfo;
+
+ private Hotseat mHotseat;
+ private View mAllAppsButton;
+
+ private SearchDropTargetBar mSearchDropTargetBar;
+ private AppsCustomizeTabHost mAppsCustomizeTabHost;
+ private AppsCustomizePagedView mAppsCustomizeContent;
+ private boolean mAutoAdvanceRunning = false;
+
+ private Bundle mSavedState;
+
+ private SpannableStringBuilder mDefaultKeySsb = null;
+
+ private boolean mWorkspaceLoading = true;
+
+ private boolean mPaused = true;
+ private boolean mRestoring;
+ private boolean mWaitingForResult;
+ private boolean mOnResumeNeedsLoad;
+
+ private Bundle mSavedInstanceState;
+
+ private LauncherModel mModel;
+ private IconCache mIconCache;
+ private boolean mUserPresent = true;
+ private boolean mVisible = false;
+ private boolean mAttached = false;
+
+ private static LocaleConfiguration sLocaleConfiguration = null;
+
+ private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+
+ private Intent mAppMarketIntent = null;
+
+ // Related to the auto-advancing of widgets
+ private final int ADVANCE_MSG = 1;
+ private final int mAdvanceInterval = 20000;
+ private final int mAdvanceStagger = 250;
+ private long mAutoAdvanceSentTime;
+ private long mAutoAdvanceTimeLeft = -1;
+ private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
+ new HashMap<View, AppWidgetProviderInfo>();
+
+ // Determines how long to wait after a rotation before restoring the screen orientation to
+ // match the sensor state.
+ private final int mRestoreScreenOrientationDelay = 500;
+
+ // External icons saved in case of resource changes, orientation, etc.
+ private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
+ private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
+ private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
+
+ static final ArrayList<String> sDumpLogs = new ArrayList<String>();
+
+
+ private BubbleTextView mWaitingForResume;
+
+ // Preferences
+ private boolean mShowSearchBar;
+
+ private Runnable mBuildLayersRunnable = new Runnable() {
+ public void run() {
+ if (mWorkspace != null) {
+ mWorkspace.buildPageHardwareLayers();
+ }
+ }
+ };
+
+ private static ArrayList<PendingAddArguments> sPendingAddList
+ = new ArrayList<PendingAddArguments>();
+
+ private static class PendingAddArguments {
+ int requestCode;
+ Intent intent;
+ long container;
+ int screen;
+ int cellX;
+ int cellY;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LauncherApplication app = ((LauncherApplication)getApplication());
+ mModel = app.setLauncher(this);
+ mIconCache = app.getIconCache();
+ mDragController = new DragController(this);
+ mInflater = getLayoutInflater();
+
+ mAppWidgetManager = AppWidgetManager.getInstance(this);
+ mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
+ mAppWidgetHost.startListening();
+
+ // Preferences
+ mShowSearchBar = PreferencesProvider.Interface.Homescreen.getShowSearchBar(this);
+
+ if (PROFILE_STARTUP) {
+ android.os.Debug.startMethodTracing(
+ Environment.getExternalStorageDirectory() + "/launcher");
+ }
+
+ checkForLocaleChange();
+ setContentView(R.layout.launcher);
+ setupViews();
+ showFirstRunWorkspaceCling();
+
+ registerContentObservers();
+
+ lockAllApps();
+
+ mSavedState = savedInstanceState;
+ restoreState(mSavedState);
+
+ // Update customization drawer _after_ restoring the states
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.onPackagesUpdated();
+ }
+
+ if (PROFILE_STARTUP) {
+ android.os.Debug.stopMethodTracing();
+ }
+
+ if (!mRestoring) {
+ mModel.startLoader(this, true);
+ }
+
+ if (!mModel.isAllAppsLoaded()) {
+ ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
+ mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
+ }
+
+ // For handling default keys
+ mDefaultKeySsb = new SpannableStringBuilder();
+ Selection.setSelection(mDefaultKeySsb, 0);
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ registerReceiver(mCloseSystemDialogsReceiver, filter);
+
+ boolean searchVisible = false;
+ boolean voiceVisible = false;
+ // If we have a saved version of these external icons, we load them up immediately
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
+ sAppMarketIcon[coi] == null) {
+ updateAppMarketIcon();
+ searchVisible = updateGlobalSearchIcon();
+ voiceVisible = updateVoiceSearchIcon(searchVisible);
+ }
+ if (sGlobalSearchIcon[coi] != null) {
+ updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
+ searchVisible = true;
+ }
+ if (sVoiceSearchIcon[coi] != null) {
+ updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
+ voiceVisible = true;
+ }
+ if (sAppMarketIcon[coi] != null) {
+ updateAppMarketIcon(sAppMarketIcon[coi]);
+ }
+ mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+
+
+ final UiModeManager uiModeManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
+ if (PreferencesProvider.Interface.General.getAutoRotate(this) ||
+ uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_NORMAL) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+ }
+ }
+
+ private void checkForLocaleChange() {
+ if (sLocaleConfiguration == null) {
+ new AsyncTask<Void, Void, LocaleConfiguration>() {
+ @Override
+ protected LocaleConfiguration doInBackground(Void... unused) {
+ LocaleConfiguration localeConfiguration = new LocaleConfiguration();
+ readConfiguration(Launcher.this, localeConfiguration);
+ return localeConfiguration;
+ }
+
+ @Override
+ protected void onPostExecute(LocaleConfiguration result) {
+ sLocaleConfiguration = result;
+ checkForLocaleChange(); // recursive, but now with a locale configuration
+ }
+ }.execute();
+ return;
+ }
+
+ final Configuration configuration = getResources().getConfiguration();
+
+ final String previousLocale = sLocaleConfiguration.locale;
+ final String locale = configuration.locale.toString();
+
+ final int previousMcc = sLocaleConfiguration.mcc;
+ final int mcc = configuration.mcc;
+
+ final int previousMnc = sLocaleConfiguration.mnc;
+ final int mnc = configuration.mnc;
+
+ boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
+
+ if (localeChanged) {
+ sLocaleConfiguration.locale = locale;
+ sLocaleConfiguration.mcc = mcc;
+ sLocaleConfiguration.mnc = mnc;
+
+ mIconCache.flush();
+
+ final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
+ new Thread("WriteLocaleConfiguration") {
+ @Override
+ public void run() {
+ writeConfiguration(Launcher.this, localeConfiguration);
+ }
+ }.start();
+ }
+ }
+
+ private static class LocaleConfiguration {
+ public String locale;
+ public int mcc = -1;
+ public int mnc = -1;
+ }
+
+ private static void readConfiguration(Context context, LocaleConfiguration configuration) {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(context.openFileInput(PREFERENCES));
+ configuration.locale = in.readUTF();
+ configuration.mcc = in.readInt();
+ configuration.mnc = in.readInt();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
+ out.writeUTF(configuration.locale);
+ out.writeInt(configuration.mcc);
+ out.writeInt(configuration.mnc);
+ out.flush();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ //noinspection ResultOfMethodCallIgnored
+ context.getFileStreamPath(PREFERENCES).delete();
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ public DragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ static int getScreen() {
+ synchronized (sLock) {
+ return sScreen;
+ }
+ }
+
+ static void setScreen(int screen) {
+ synchronized (sLock) {
+ sScreen = screen;
+ }
+ }
+
+ /**
+ * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
+ * a configuration step, this allows the proper animations to run after other transitions.
+ */
+ private boolean completeAdd(PendingAddArguments args) {
+ boolean result = false;
+ switch (args.requestCode) {
+ case REQUEST_PICK_APPLICATION:
+ completeAddApplication(args.intent, args.container, args.screen, args.cellX,
+ args.cellY);
+ break;
+ case REQUEST_PICK_SHORTCUT:
+ processShortcut(args.intent);
+ break;
+ case REQUEST_CREATE_SHORTCUT:
+ completeAddShortcut(args.intent, args.container, args.screen, args.cellX,
+ args.cellY);
+ result = true;
+ break;
+ case REQUEST_PICK_APPWIDGET:
+ addAppWidgetFromPick(args.intent);
+ break;
+ case REQUEST_CREATE_APPWIDGET:
+ int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ completeAddAppWidget(appWidgetId, args.container, args.screen);
+ result = true;
+ break;
+ case REQUEST_PICK_WALLPAPER:
+ // We just wanted the activity result here so we can clear mWaitingForResult
+ break;
+ }
+ // In any situation where we have a multi-step drop, we should reset the add info only after
+ // we complete the drop
+ resetAddInfo();
+ return result;
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, int resultCode, final Intent data) {
+ boolean delayExitSpringLoadedMode = false;
+ mWaitingForResult = false;
+
+ // The pattern used here is that a user PICKs a specific application,
+ // which, depending on the target, might need to CREATE the actual target.
+
+ // For example, the user would PICK_SHORTCUT for "Music playlist", and we
+ // launch over to the Music app to actually CREATE_SHORTCUT.
+ if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
+ final PendingAddArguments args = new PendingAddArguments();
+ args.requestCode = requestCode;
+ args.intent = data;
+ args.container = mPendingAddInfo.container;
+ args.screen = mPendingAddInfo.screen;
+ args.cellX = mPendingAddInfo.cellX;
+ args.cellY = mPendingAddInfo.cellY;
+
+ // If the loader is still running, defer the add until it is done.
+ if (isWorkspaceLocked()) {
+ sPendingAddList.add(args);
+ } else {
+ delayExitSpringLoadedMode = completeAdd(args);
+ }
+ } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
+ requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED) {
+ if (data != null) {
+ // Clean up the appWidgetId if we canceled
+ int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ if (appWidgetId != -1) {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ }
+ }
+ }
+
+ // Exit spring loaded mode if necessary after cancelling the configuration of a widget
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mPaused = false;
+ // Restart launcher when preferences are changed
+ if (preferencesChanged()) {
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+ if (mRestoring || mOnResumeNeedsLoad) {
+ mWorkspaceLoading = true;
+ mModel.startLoader(this, true);
+ mRestoring = false;
+ mOnResumeNeedsLoad = false;
+ }
+ if (mWaitingForResume != null) {
+ mWaitingForResume.setStayPressed(false);
+ }
+ // When we resume Launcher, a different Activity might be responsible for the app
+ // market intent, so refresh the icon
+ updateAppMarketIcon();
+ mAppsCustomizeTabHost.onResume();
+ if (!mWorkspaceLoading) {
+ final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
+ final Workspace workspace = mWorkspace;
+ // We want to let Launcher draw itself at least once before we force it to build
+ // layers on all the workspace pages, so that transitioning to Launcher from other
+ // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to
+ // a true draw so we wait until the second preDraw call to be safe
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ boolean mFirstTime = true;
+ public boolean onPreDraw() {
+ if (mFirstTime) {
+ mFirstTime = false;
+ } else {
+ workspace.post(mBuildLayersRunnable);
+ observer.removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ }
+ clearTypedText();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mPaused = true;
+ mDragController.cancelDrag();
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ // Flag the loader to stop early before switching
+ mModel.stopLoader();
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.surrender();
+ }
+ return Boolean.TRUE;
+ }
+
+ // We can't hide the IME if it was forced open. So don't bother
+ /*
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+
+ if (hasFocus) {
+ final InputMethodManager inputManager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
+ android.os.Handler()) {
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
+ }
+ });
+ Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
+ }
+ }
+ */
+
+ private boolean acceptFilter() {
+ final InputMethodManager inputManager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ return !inputManager.isFullscreenMode();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ final int uniChar = event.getUnicodeChar();
+ final boolean handled = super.onKeyDown(keyCode, event);
+ final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
+ if (!handled && acceptFilter() && isKeyNotWhitespace) {
+ boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
+ keyCode, event);
+ if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
+ // something usable has been typed - start a search
+ // the typed text will be retrieved and cleared by
+ // showSearchDialog()
+ // If there are multiple keystrokes before the search dialog takes focus,
+ // onSearchRequested() will be called for every keystroke,
+ // but it is idempotent, so it's fine.
+ return onSearchRequested();
+ }
+ }
+
+ // Eat the long press event so the keyboard doesn't come up.
+ if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
+ return true;
+ }
+
+ return handled;
+ }
+
+ private String getTypedText() {
+ return mDefaultKeySsb.toString();
+ }
+
+ private void clearTypedText() {
+ mDefaultKeySsb.clear();
+ mDefaultKeySsb.clearSpans();
+ Selection.setSelection(mDefaultKeySsb, 0);
+ }
+
+ /**
+ * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
+ * State
+ */
+ private static State intToState(int stateOrdinal) {
+ State state = State.WORKSPACE;
+ final State[] stateValues = State.values();
+ for (int i = 0; i < stateValues.length; i++) {
+ if (stateValues[i].ordinal() == stateOrdinal) {
+ state = stateValues[i];
+ break;
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Restores the previous state, if it exists.
+ *
+ * @param savedState The previous state.
+ */
+ private void restoreState(Bundle savedState) {
+ if (savedState == null) {
+ return;
+ }
+
+ State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+ if (state == State.APPS_CUSTOMIZE) {
+ showAllApps(false);
+ }
+
+ final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
+ if (currentScreen > -1) {
+ mWorkspace.setCurrentPage(currentScreen);
+ }
+
+ final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
+ final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
+
+ if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
+ mPendingAddInfo.container = pendingAddContainer;
+ mPendingAddInfo.screen = pendingAddScreen;
+ mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
+ mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
+ mRestoring = true;
+ }
+
+ boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
+ if (renameFolder) {
+ long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
+ mFolderInfo = mModel.getFolderById(this, sFolders, id);
+ mRestoring = true;
+ }
+
+
+ // Restore the AppsCustomize tab
+ if (mAppsCustomizeTabHost != null) {
+ String curTab = savedState.getString("apps_customize_currentTab");
+ if (curTab != null) {
+ // We set this directly so that there is no delay before the tab is set
+ mAppsCustomizeContent.setContentType(
+ mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
+ mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
+ mAppsCustomizeContent.loadAssociatedPages(
+ mAppsCustomizeContent.getCurrentPage());
+ }
+
+ int currentIndex = savedState.getInt("apps_customize_currentIndex");
+ mAppsCustomizeContent.restorePageForIndex(currentIndex);
+ }
+ }
+
+ /**
+ * Finds all the views we need and configure them properly.
+ */
+ private void setupViews() {
+ final DragController dragController = mDragController;
+
+ mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
+ mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
+ mQsbDivider = (ImageView) findViewById(R.id.qsb_divider);
+ mDockDivider = (ImageView) findViewById(R.id.dock_divider);
+
+ // Setup the drag layer
+ mDragLayer.setup(this, dragController);
+
+ // Setup the hotseat
+ mHotseat = (Hotseat) findViewById(R.id.hotseat);
+ if (mHotseat != null) {
+ mHotseat.setup(this);
+ }
+
+ // Setup the workspace
+ mWorkspace.setHapticFeedbackEnabled(false);
+ mWorkspace.setOnLongClickListener(this);
+ mWorkspace.setup(dragController);
+ dragController.addDragListener(mWorkspace);
+
+ // Get the search/delete bar
+ mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
+
+ // Hide the search divider if we are hiding search bar
+ if (!mShowSearchBar && getCurrentOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ ((View) findViewById(R.id.qsb_divider)).setVisibility(View.GONE);
+ }
+
+ // Setup AppsCustomize
+ mAppsCustomizeTabHost = (AppsCustomizeTabHost)
+ findViewById(R.id.apps_customize_pane);
+ mAppsCustomizeContent = (AppsCustomizePagedView)
+ mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
+ mAppsCustomizeContent.setup(this, dragController);
+
+ // Get the all apps button
+ mAllAppsButton = findViewById(R.id.all_apps_button);
+ if (mAllAppsButton != null) {
+ mAllAppsButton.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ onTouchDownAllAppsButton(v);
+ }
+ return false;
+ }
+ });
+ }
+ // Setup the drag controller (drop targets have to be added in reverse order in priority)
+ dragController.setDragScoller(mWorkspace);
+ dragController.setScrollView(mDragLayer);
+ dragController.setMoveTarget(mWorkspace);
+ dragController.addDropTarget(mWorkspace);
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.setup(this, dragController);
+ }
+ }
+
+ /**
+ * Creates a view representing a shortcut.
+ *
+ * @param info The data structure describing the shortcut.
+ *
+ * @return A View inflated from R.layout.application.
+ */
+ View createShortcut(ShortcutInfo info) {
+ return createShortcut(R.layout.application,
+ (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+ }
+
+ /**
+ * Creates a view representing a shortcut inflated from the specified resource.
+ *
+ * @param layoutResId The id of the XML layout used to create the shortcut.
+ * @param parent The group the shortcut belongs to.
+ * @param info The data structure describing the shortcut.
+ *
+ * @return A View inflated from layoutResId.
+ */
+ View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
+ BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
+ favorite.applyFromShortcutInfo(info, mIconCache);
+ favorite.setOnClickListener(this);
+ return favorite;
+ }
+
+ /**
+ * Add an application shortcut to the workspace.
+ *
+ * @param data The intent describing the application.
+ * @param cellInfo The position on screen where to create the shortcut.
+ */
+ void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) {
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ final CellLayout layout = getCellLayout(container, screen);
+
+ // First we check if we already know the exact location where we want to add this item.
+ if (cellX >= 0 && cellY >= 0) {
+ cellXY[0] = cellX;
+ cellXY[1] = cellY;
+ } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
+ showOutOfSpaceMessage();
+ return;
+ }
+
+ final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
+
+ if (info != null) {
+ info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ info.container = ItemInfo.NO_ID;
+ mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1],
+ isWorkspaceLocked(), cellX, cellY);
+ } else {
+ Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
+ }
+ }
+
+ /**
+ * Add a shortcut to the workspace.
+ *
+ * @param data The intent describing the shortcut.
+ * @param cellInfo The position on screen where to create the shortcut.
+ */
+ private void completeAddShortcut(Intent data, long container, int screen, int cellX,
+ int cellY) {
+ int[] cellXY = mTmpAddItemCellCoordinates;
+ int[] touchXY = mPendingAddInfo.dropPos;
+ CellLayout layout = getCellLayout(container, screen);
+
+ boolean foundCellSpan = false;
+
+ ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
+ if (info == null) {
+ return;
+ }
+ final View view = createShortcut(info);
+
+ // First we check if we already know the exact location where we want to add this item.
+ if (cellX >= 0 && cellY >= 0) {
+ cellXY[0] = cellX;
+ cellXY[1] = cellY;
+ foundCellSpan = true;
+
+ // If appropriate, either create a folder or add to an existing folder
+ if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY,
+ true, null,null)) {
+ return;
+ }
+ DragObject dragObject = new DragObject();
+ dragObject.dragInfo = info;
+ if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, dragObject, true)) {
+ return;
+ }
+ } else if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
+ foundCellSpan = (result != null);
+ } else {
+ foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
+ }
+
+ if (!foundCellSpan) {
+ showOutOfSpaceMessage();
+ return;
+ }
+
+ LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false);
+
+ if (!mRestoring) {
+ mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1,
+ isWorkspaceLocked());
+ }
+ }
+
+ int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight, int[] spanXY) {
+ if (spanXY == null) {
+ spanXY = new int[2];
+ }
+
+ Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
+ // We want to account for the extra amount of padding that we are adding to the widget
+ // to ensure that it gets the full amount of space that it has requested
+ int requiredWidth = minWidth + padding.left + padding.right;
+ int requiredHeight = minHeight + padding.top + padding.bottom;
+ return CellLayout.rectToCell(getResources(), requiredWidth, requiredHeight, null);
+ }
+
+ int[] getSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
+ return getSpanForWidget(info.provider, info.minWidth, info.minHeight, spanXY);
+ }
+
+ int[] getMinResizeSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
+ return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight, spanXY);
+ }
+
+ int[] getSpanForWidget(PendingAddWidgetInfo info, int[] spanXY) {
+ return getSpanForWidget(info.componentName, info.minWidth, info.minHeight, spanXY);
+ }
+
+ /**
+ * Add a widget to the workspace.
+ *
+ * @param appWidgetId The app widget id
+ * @param cellInfo The position on screen where to create the widget.
+ */
+ private void completeAddAppWidget(final int appWidgetId, long container, int screen) {
+ AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+
+ // Calculate the grid spans needed to fit this widget
+ CellLayout layout = getCellLayout(container, screen);
+
+ int[] spanXY = getSpanForWidget(appWidgetInfo, null);
+
+ // Try finding open space on Launcher screen
+ // We have saved the position to which the widget was dragged-- this really only matters
+ // if we are placing widgets on a "spring-loaded" screen
+ int[] cellXY = mTmpAddItemCellCoordinates;
+ int[] touchXY = mPendingAddInfo.dropPos;
+ boolean foundCellSpan = false;
+ if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
+ cellXY[0] = mPendingAddInfo.cellX;
+ cellXY[1] = mPendingAddInfo.cellY;
+ foundCellSpan = true;
+ } else if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ int[] result = layout.findNearestVacantArea(
+ touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
+ foundCellSpan = (result != null);
+ } else {
+ foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]);
+ }
+
+ if (!foundCellSpan) {
+ if (appWidgetId != -1) {
+ // Deleting an app widget ID is a void call but writes to disk before returning
+ // to the caller...
+ new Thread("deleteAppWidgetId") {
+ public void run() {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ }
+ }.start();
+ }
+ showOutOfSpaceMessage();
+ return;
+ }
+
+ // Build Launcher-specific widget info and save to database
+ LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
+ launcherInfo.spanX = spanXY[0];
+ launcherInfo.spanY = spanXY[1];
+
+ LauncherModel.addItemToDatabase(this, launcherInfo,
+ container, screen, cellXY[0], cellXY[1], false);
+
+ if (!mRestoring) {
+ // Perform actual inflation because we're live
+ launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+
+ launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+ launcherInfo.hostView.setTag(launcherInfo);
+
+ mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
+ launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
+
+ addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mUserPresent = false;
+ mDragLayer.clearAllResizeFrames();
+ updateRunning();
+
+ // Reset AllApps to its initial state only if we are not in the middle of
+ // processing a multi-step drop
+ if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
+ mAppsCustomizeTabHost.reset();
+ showWorkspace(false);
+ }
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ mUserPresent = true;
+ updateRunning();
+ }
+ }
+ };
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Listen for broadcasts related to user-presence
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ registerReceiver(mReceiver, filter);
+
+ mAttached = true;
+ mVisible = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mVisible = false;
+ mDragLayer.clearAllResizeFrames();
+
+ if (mAttached) {
+ unregisterReceiver(mReceiver);
+ mAttached = false;
+ }
+ updateRunning();
+ }
+
+ public void onWindowVisibilityChanged(int visibility) {
+ mVisible = visibility == View.VISIBLE;
+ updateRunning();
+ }
+
+ private void sendAdvanceMessage(long delay) {
+ mHandler.removeMessages(ADVANCE_MSG);
+ Message msg = mHandler.obtainMessage(ADVANCE_MSG);
+ mHandler.sendMessageDelayed(msg, delay);
+ mAutoAdvanceSentTime = System.currentTimeMillis();
+ }
+
+ private void updateRunning() {
+ boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
+ if (autoAdvanceRunning != mAutoAdvanceRunning) {
+ mAutoAdvanceRunning = autoAdvanceRunning;
+ if (autoAdvanceRunning) {
+ long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
+ sendAdvanceMessage(delay);
+ } else {
+ if (!mWidgetsToAdvance.isEmpty()) {
+ mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
+ (System.currentTimeMillis() - mAutoAdvanceSentTime));
+ }
+ mHandler.removeMessages(ADVANCE_MSG);
+ mHandler.removeMessages(0); // Remove messages sent using postDelayed()
+ }
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == ADVANCE_MSG) {
+ int i = 0;
+ for (View key: mWidgetsToAdvance.keySet()) {
+ final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
+ final int delay = mAdvanceStagger * i;
+ if (v instanceof Advanceable) {
+ postDelayed(new Runnable() {
+ public void run() {
+ ((Advanceable) v).advance();
+ }
+ }, delay);
+ }
+ i++;
+ }
+ sendAdvanceMessage(mAdvanceInterval);
+ }
+ }
+ };
+
+ void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
+ if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
+ View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
+ if (v instanceof Advanceable) {
+ mWidgetsToAdvance.put(hostView, appWidgetInfo);
+ ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
+ updateRunning();
+ }
+ }
+
+ void removeWidgetToAutoAdvance(View hostView) {
+ if (mWidgetsToAdvance.containsKey(hostView)) {
+ mWidgetsToAdvance.remove(hostView);
+ updateRunning();
+ }
+ }
+
+ public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
+ removeWidgetToAutoAdvance(launcherInfo.hostView);
+ launcherInfo.hostView = null;
+ }
+
+ void showOutOfSpaceMessage() {
+ Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
+ }
+
+ public LauncherAppWidgetHost getAppWidgetHost() {
+ return mAppWidgetHost;
+ }
+
+ public LauncherModel getModel() {
+ return mModel;
+ }
+
+ void closeSystemDialogs() {
+ getWindow().closeAllPanels();
+
+ /**
+ * We should remove this code when we remove all the dialog code.
+ try {
+ dismissDialog(DIALOG_CREATE_SHORTCUT);
+ // Unlock the workspace if the dialog was showing
+ } catch (Exception e) {
+ // An exception is thrown if the dialog is not visible, which is fine
+ }
+
+ try {
+ dismissDialog(DIALOG_RENAME_FOLDER);
+ // Unlock the workspace if the dialog was showing
+ } catch (Exception e) {
+ // An exception is thrown if the dialog is not visible, which is fine
+ }
+ */
+
+ // Whatever we were doing is hereby canceled.
+ mWaitingForResult = false;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ // Close the menu
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // also will cancel mWaitingForResult.
+ closeSystemDialogs();
+
+ boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+
+ Folder openFolder = mWorkspace.getOpenFolder();
+ // In all these cases, only animate if we're already on home
+ mWorkspace.exitWidgetResizeMode();
+ if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+ openFolder == null) {
+ mWorkspace.moveToDefaultScreen(true);
+ }
+
+ closeFolder();
+ exitSpringLoadedDragMode();
+ showWorkspace(alreadyOnHome);
+
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ InputMethodManager imm = (InputMethodManager)getSystemService(
+ INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+
+ // Reset AllApps to its initial state
+ if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
+ mAppsCustomizeTabHost.reset();
+ }
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ // Do not call super here
+ mSavedInstanceState = savedInstanceState;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(RUNTIME_STATE, mState.ordinal());
+ // We close any open folder since it will not be re-opened, and we need to make sure
+ // this state is reflected.
+ closeFolder();
+
+ if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 &&
+ mWaitingForResult) {
+ outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
+ }
+
+ if (mFolderInfo != null && mWaitingForResult) {
+ outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
+ outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
+ }
+
+ // Save the current AppsCustomize tab
+ if (mAppsCustomizeTabHost != null) {
+ String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
+ if (currentTabTag != null) {
+ outState.putString("apps_customize_currentTab", currentTabTag);
+ }
+ int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
+ outState.putInt("apps_customize_currentIndex", currentIndex);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Remove all pending runnables
+ mHandler.removeMessages(ADVANCE_MSG);
+ mHandler.removeMessages(0);
+ mWorkspace.removeCallbacks(mBuildLayersRunnable);
+
+ // Stop callbacks from LauncherModel
+ LauncherApplication app = ((LauncherApplication) getApplication());
+ mModel.stopLoader();
+ app.setLauncher(null);
+
+ try {
+ mAppWidgetHost.stopListening();
+ } catch (NullPointerException ex) {
+ Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
+ }
+ mAppWidgetHost = null;
+
+ mWidgetsToAdvance.clear();
+
+ TextKeyListener.getInstance().release();
+
+
+ unbindWorkspaceAndHotseatItems();
+
+ getContentResolver().unregisterContentObserver(mWidgetObserver);
+ unregisterReceiver(mCloseSystemDialogsReceiver);
+
+ ((ViewGroup) mWorkspace.getParent()).removeAllViews();
+ mWorkspace.removeAllViews();
+ mWorkspace = null;
+ mDragController = null;
+
+ ValueAnimator.clearAllAnimations();
+ }
+
+ public DragController getDragController() {
+ return mDragController;
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ if (requestCode >= 0) mWaitingForResult = true;
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * Indicates that we want global search for this activity by setting the globalSearch
+ * argument for {@link #startSearch} to true.
+ */
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+
+ showWorkspace(true);
+
+ if (initialQuery == null) {
+ // Use any text typed in the launcher as the initial query
+ initialQuery = getTypedText();
+ }
+ if (appSearchData == null) {
+ appSearchData = new Bundle();
+ appSearchData.putString(Search.SOURCE, "launcher-search");
+ }
+ Rect sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
+
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ appSearchData, globalSearch, sourceBounds);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (isWorkspaceLocked()) {
+ return false;
+ }
+
+ super.onCreateOptionsMenu(menu);
+
+ Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS);
+ manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
+ settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ Intent preferences = new Intent().setClass(this, Preferences.class);
+ preferences.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ String helpUrl = getString(R.string.help_url);
+ Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl));
+ help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
+ .setIcon(android.R.drawable.ic_menu_gallery)
+ .setAlphabeticShortcut('W');
+ menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
+ .setIcon(android.R.drawable.ic_menu_manage)
+ .setIntent(manageApps)
+ .setAlphabeticShortcut('M');
+ menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings)
+ .setIcon(android.R.drawable.ic_menu_preferences)
+ .setIntent(settings)
+ .setAlphabeticShortcut('P');
+ menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences)
+ .setIcon(android.R.drawable.ic_menu_preferences)
+ .setIntent(preferences)
+ .setAlphabeticShortcut('O');
+ if (!helpUrl.isEmpty()) {
+ menu.add(0, MENU_HELP, 0, R.string.menu_help)
+ .setIcon(android.R.drawable.ic_menu_help)
+ .setIntent(help)
+ .setAlphabeticShortcut('H');
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ if (mAppsCustomizeTabHost.isTransitioning()) {
+ return false;
+ }
+ boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE);
+ menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible);
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_WALLPAPER_SETTINGS:
+ startWallpaper();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ startSearch(null, false, null, true);
+ // Use a custom animation for launching search
+ overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
+ return true;
+ }
+
+ public boolean isWorkspaceLocked() {
+ return mWorkspaceLoading || mWaitingForResult;
+ }
+
+ private void resetAddInfo() {
+ mPendingAddInfo.container = ItemInfo.NO_ID;
+ mPendingAddInfo.screen = -1;
+ mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
+ mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
+ mPendingAddInfo.dropPos = null;
+ }
+
+ void addAppWidgetFromPick(Intent data) {
+ // TODO: catch bad widget exception when sent
+ int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ // TODO: Is this log message meaningful?
+ if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras());
+ addAppWidgetImpl(appWidgetId, null);
+ }
+
+ void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
+ AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+
+ if (appWidget.configure != null) {
+ // Launch over to configure widget, if needed
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ intent.setComponent(appWidget.configure);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ if (info != null) {
+ if (info.mimeType != null && !info.mimeType.isEmpty()) {
+ intent.putExtra(
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
+ info.mimeType);
+
+ final String mimeType = info.mimeType;
+ final ClipData clipData = (ClipData) info.configurationData;
+ final ClipDescription clipDesc = clipData.getDescription();
+ for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
+ if (clipDesc.getMimeType(i).equals(mimeType)) {
+ final ClipData.Item item = clipData.getItemAt(i);
+ final CharSequence stringData = item.getText();
+ final Uri uriData = item.getUri();
+ final Intent intentData = item.getIntent();
+ final String key =
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
+ if (uriData != null) {
+ intent.putExtra(key, uriData);
+ } else if (intentData != null) {
+ intent.putExtra(key, intentData);
+ } else if (stringData != null) {
+ intent.putExtra(key, stringData);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
+ } else {
+ // Otherwise just add it
+ completeAddAppWidget(appWidgetId, info.container, info.screen);
+
+ // Exit spring loaded mode if necessary after adding the widget
+ exitSpringLoadedDragModeDelayed(true, false);
+ }
+ }
+
+ /**
+ * Process a shortcut drop.
+ *
+ * @param componentName The name of the component
+ * @param screen The screen where it should be added
+ * @param cell The cell it should be added to, optional
+ * @param position The location on the screen where it was dropped, optional
+ */
+ void processShortcutFromDrop(ComponentName componentName, long container, int screen,
+ int[] cell, int[] loc) {
+ resetAddInfo();
+ mPendingAddInfo.container = container;
+ mPendingAddInfo.screen = screen;
+ mPendingAddInfo.dropPos = loc;
+
+ if (cell != null) {
+ mPendingAddInfo.cellX = cell[0];
+ mPendingAddInfo.cellY = cell[1];
+ }
+
+ Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ createShortcutIntent.setComponent(componentName);
+ processShortcut(createShortcutIntent);
+ }
+
+ /**
+ * Process a widget drop.
+ *
+ * @param info The PendingAppWidgetInfo of the widget being added.
+ * @param screen The screen where it should be added
+ * @param cell The cell it should be added to, optional
+ * @param position The location on the screen where it was dropped, optional
+ */
+ void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen,
+ int[] cell, int[] loc) {
+ resetAddInfo();
+ mPendingAddInfo.container = info.container = container;
+ mPendingAddInfo.screen = info.screen = screen;
+ mPendingAddInfo.dropPos = loc;
+ if (cell != null) {
+ mPendingAddInfo.cellX = cell[0];
+ mPendingAddInfo.cellY = cell[1];
+ }
+
+ int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+ AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+ addAppWidgetImpl(appWidgetId, info);
+ }
+
+ void processShortcut(Intent intent) {
+ // Handle case where user selected "Applications"
+ String applicationName = getResources().getString(R.string.group_applications);
+ String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
+ if (applicationName != null && applicationName.equals(shortcutName)) {
+ Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+ pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
+ pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
+ startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
+ } else {
+ startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
+ }
+ }
+
+ void processWallpaper(Intent intent) {
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
+ }
+
+ FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX,
+ int cellY) {
+ final FolderInfo folderInfo = new FolderInfo();
+ folderInfo.title = getText(R.string.folder_name);
+
+ // Update the model
+ LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY,
+ false);
+ sFolders.put(folderInfo.id, folderInfo);
+
+ // Create the view
+ FolderIcon newFolder =
+ FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
+ mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1,
+ isWorkspaceLocked());
+ return newFolder;
+ }
+
+ void removeFolder(FolderInfo folder) {
+ sFolders.remove(folder.id);
+ }
+
+ private void startWallpaper() {
+ showWorkspace(true);
+ final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
+ Intent chooser = Intent.createChooser(pickWallpaper,
+ getText(R.string.chooser_wallpaper));
+ // NOTE: Adds a configure option to the chooser if the wallpaper supports it
+ // Removed in Eclair MR1
+// WallpaperManager wm = (WallpaperManager)
+// getSystemService(Context.WALLPAPER_SERVICE);
+// WallpaperInfo wi = wm.getWallpaperInfo();
+// if (wi != null && wi.getSettingsActivity() != null) {
+// LabeledIntent li = new LabeledIntent(getPackageName(),
+// R.string.configure_wallpaper, 0);
+// li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
+// chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
+// }
+ startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
+ }
+
+ /**
+ * Registers various content observers. The current implementation registers
+ * only a favorites observer to keep track of the favorites applications.
+ */
+ private void registerContentObservers() {
+ ContentResolver resolver = getContentResolver();
+ resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
+ true, mWidgetObserver);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_HOME:
+ return true;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) {
+ dumpState();
+ return true;
+ }
+ break;
+ }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_HOME:
+ return true;
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mState == State.APPS_CUSTOMIZE) {
+ showWorkspace(true);
+ } else if (mWorkspace.getOpenFolder() != null) {
+ Folder openFolder = mWorkspace.getOpenFolder();
+ if (openFolder.isEditingName()) {
+ openFolder.dismissEditingName();
+ } else {
+ closeFolder();
+ }
+ } else {
+ mWorkspace.exitWidgetResizeMode();
+
+ // Back button is a no-op here, but give at least some feedback for the button press
+ mWorkspace.showOutlinesTemporarily();
+ }
+ }
+
+ /**
+ * Re-listen when widgets are reset.
+ */
+ private void onAppWidgetReset() {
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
+ }
+ }
+
+ /**
+ * Go through the and disconnect any of the callbacks in the drawables and the views or we
+ * leak the previous Home screen on orientation change.
+ */
+ private void unbindWorkspaceAndHotseatItems() {
+ if (mModel != null) {
+ mModel.unbindWorkspaceItems();
+ }
+ }
+
+ /**
+ * Launches the intent referred by the clicked shortcut.
+ *
+ * @param v The view representing the clicked shortcut.
+ */
+ public void onClick(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) {
+ return;
+ }
+
+ if (mWorkspace.isSwitchingState()) {
+ return;
+ }
+
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ // Open shortcut
+ final Intent intent = ((ShortcutInfo) tag).intent;
+ int[] pos = new int[2];
+ v.getLocationOnScreen(pos);
+ intent.setSourceBounds(new Rect(pos[0], pos[1],
+ pos[0] + v.getWidth(), pos[1] + v.getHeight()));
+ boolean success = startActivitySafely(intent, tag);
+
+ if (success && v instanceof BubbleTextView) {
+ mWaitingForResume = (BubbleTextView) v;
+ mWaitingForResume.setStayPressed(true);
+ }
+ } else if (tag instanceof FolderInfo) {
+ if (v instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) v;
+ handleFolderClick(fi);
+ }
+ } else if (v == mAllAppsButton) {
+ if (mState == State.APPS_CUSTOMIZE) {
+ showWorkspace(true);
+ } else {
+ onClickAllAppsButton(v);
+ }
+ }
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ // this is an intercepted event being forwarded from mWorkspace;
+ // clicking anywhere on the workspace causes the customization drawer to slide down
+ showWorkspace(true);
+ return false;
+ }
+
+ /**
+ * Event handler for the search button
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickSearchButton(View v) {
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ onSearchRequested();
+ }
+
+ /**
+ * Event handler for the voice button
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickVoiceButton(View v) {
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+
+ /**
+ * Event handler for the "grid" button that appears on the home screen, which
+ * enters all apps mode.
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickAllAppsButton(View v) {
+ showAllApps(true);
+ }
+
+ public void onTouchDownAllAppsButton(View v) {
+ // Provide the same haptic feedback that the system offers for virtual keys.
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+
+ public void onClickAppMarketButton(View v) {
+ if (mAppMarketIntent != null) {
+ startActivitySafely(mAppMarketIntent, "app market");
+ }
+ }
+
+ void startApplicationDetailsActivity(ComponentName componentName) {
+ String packageName = componentName.getPackageName();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+
+ void startApplicationUninstallActivity(ApplicationInfo appInfo) {
+ if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
+ // System applications cannot be installed. For now, show a toast explaining that.
+ // We may give them the option of disabling apps this way.
+ int messageId = R.string.uninstall_system_app_text;
+ Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
+ } else {
+ String packageName = appInfo.componentName.getPackageName();
+ String className = appInfo.componentName.getClassName();
+ Intent intent = new Intent(
+ Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+ }
+
+ boolean startActivitySafely(Intent intent, Object tag) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
+ } catch (SecurityException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity. "
+ + "tag="+ tag + " intent=" + intent, e);
+ }
+ return false;
+ }
+
+ void startActivityForResultSafely(Intent intent, int requestCode) {
+ try {
+ startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ } catch (SecurityException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity.", e);
+ }
+ }
+
+ private void handleFolderClick(FolderIcon folderIcon) {
+ final FolderInfo info = folderIcon.mInfo;
+ Folder openFolder = mWorkspace.getFolderForTag(info);
+
+ // If the folder info reports that the associated folder is open, then verify that
+ // it is actually opened. There have been a few instances where this gets out of sync.
+ if (info.opened && openFolder == null) {
+ Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
+ + info.screen + " (" + info.cellX + ", " + info.cellY + ")");
+ info.opened = false;
+ }
+
+ if (!info.opened) {
+ // Close any open folder
+ closeFolder();
+ // Open the requested folder
+ openFolder(folderIcon);
+ } else {
+ // Find the open folder...
+ int folderScreen;
+ if (openFolder != null) {
+ folderScreen = mWorkspace.getPageForView(openFolder);
+ // .. and close it
+ closeFolder(openFolder);
+ if (folderScreen != mWorkspace.getCurrentPage()) {
+ // Close any folder open on the current screen
+ closeFolder();
+ // Pull the folder onto this screen
+ openFolder(folderIcon);
+ }
+ }
+ }
+ }
+
+ private void growAndFadeOutFolderIcon(FolderIcon fi) {
+ if (fi == null) return;
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
+
+ FolderInfo info = (FolderInfo) fi.getTag();
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ CellLayout cl = (CellLayout) fi.getParent().getParent();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+ oa.start();
+ }
+
+ private void shrinkAndFadeInFolderIcon(FolderIcon fi) {
+ if (fi == null) return;
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+
+ FolderInfo info = (FolderInfo) fi.getTag();
+ CellLayout cl = null;
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ cl = (CellLayout) fi.getParent().getParent();
+ }
+
+ final CellLayout layout = cl;
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (layout != null) {
+ layout.clearFolderLeaveBehind();
+ }
+ }
+ });
+ oa.start();
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ *
+ * @param folderInfo The FolderInfo describing the folder to open.
+ */
+ public void openFolder(FolderIcon folderIcon) {
+ Folder folder = folderIcon.mFolder;
+ FolderInfo info = folder.mInfo;
+
+ growAndFadeOutFolderIcon(folderIcon);
+ info.opened = true;
+
+ // Just verify that the folder hasn't already been added to the DragLayer.
+ // There was a one-off crash where the folder had a parent already.
+ if (folder.getParent() == null) {
+ mDragLayer.addView(folder);
+ mDragController.addDropTarget((DropTarget) folder);
+ } else {
+ Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
+ folder.getParent() + ").");
+ }
+ folder.animateOpen();
+ }
+
+ public void closeFolder() {
+ Folder folder = mWorkspace.getOpenFolder();
+ if (folder != null) {
+ if (folder.isEditingName()) {
+ folder.dismissEditingName();
+ }
+ closeFolder(folder);
+
+ // Dismiss the folder cling
+ dismissFolderCling(null);
+ }
+ }
+
+ void closeFolder(Folder folder) {
+ folder.getInfo().opened = false;
+
+ ViewGroup parent = (ViewGroup) folder.getParent().getParent();
+ if (parent != null) {
+ FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
+ shrinkAndFadeInFolderIcon(fi);
+ }
+ folder.animateClosed();
+ }
+
+ public boolean onLongClick(View v) {
+ if (mState != State.WORKSPACE) {
+ return false;
+ }
+
+ if (isWorkspaceLocked()) {
+ return false;
+ }
+
+ if (!(v instanceof CellLayout)) {
+ v = (View) v.getParent().getParent();
+ }
+
+ resetAddInfo();
+ CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
+ // This happens when long clicking an item with the dpad/trackball
+ if (longClickCellInfo == null) {
+ return true;
+ }
+
+ // The hotseat touch handling does not go through Workspace, and we always allow long press
+ // on hotseat items.
+ final View itemUnderLongClick = longClickCellInfo.cell;
+ boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
+ if (allowLongPress && !mDragController.isDragging()) {
+ if (itemUnderLongClick == null) {
+ // User long pressed on empty space
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ startWallpaper();
+ } else {
+ if (!(itemUnderLongClick instanceof Folder)) {
+ // User long pressed on an item
+ mWorkspace.startDrag(longClickCellInfo);
+ }
+ }
+ }
+ return true;
+ }
+
+ boolean isHotseatLayout(View layout) {
+ return mHotseat != null && layout != null &&
+ (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
+ }
+ Hotseat getHotseat() {
+ return mHotseat;
+ }
+ SearchDropTargetBar getSearchBar() {
+ return mSearchDropTargetBar;
+ }
+
+ /**
+ * Returns the CellLayout of the specified container at the specified screen.
+ */
+ CellLayout getCellLayout(long container, int screen) {
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ if (mHotseat != null) {
+ return mHotseat.getLayout();
+ } else {
+ return null;
+ }
+ } else {
+ return (CellLayout) mWorkspace.getChildAt(screen);
+ }
+ }
+
+ Workspace getWorkspace() {
+ return mWorkspace;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_CREATE_SHORTCUT:
+ return new CreateShortcut().createDialog();
+ case DIALOG_RENAME_FOLDER:
+ return new RenameFolder().createDialog();
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ switch (id) {
+ case DIALOG_CREATE_SHORTCUT:
+ break;
+ case DIALOG_RENAME_FOLDER:
+ if (mFolderInfo != null) {
+ EditText input = (EditText) dialog.findViewById(R.id.folder_name);
+ final CharSequence text = mFolderInfo.title;
+ input.setText(text);
+ input.setSelection(0, text.length());
+ }
+ break;
+ }
+ }
+
+ void showRenameDialog(FolderInfo info) {
+ mFolderInfo = info;
+ mWaitingForResult = true;
+ showDialog(DIALOG_RENAME_FOLDER);
+ }
+
+ private void showAddDialog() {
+ resetAddInfo();
+ mPendingAddInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ mPendingAddInfo.screen = mWorkspace.getCurrentPage();
+ mWaitingForResult = true;
+ showDialog(DIALOG_CREATE_SHORTCUT);
+ }
+
+ private class RenameFolder {
+ private EditText mInput;
+
+ Dialog createDialog() {
+ final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
+ mInput = (EditText) layout.findViewById(R.id.folder_name);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
+ builder.setIcon(0);
+ builder.setTitle(getString(R.string.rename_folder_title));
+ builder.setCancelable(true);
+ builder.setOnCancelListener(new Dialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cleanup();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ cleanup();
+ }
+ }
+ );
+ builder.setPositiveButton(getString(R.string.rename_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ changeFolderName();
+ }
+ }
+ );
+ builder.setView(layout);
+
+ final AlertDialog dialog = builder.create();
+ dialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ public void onShow(DialogInterface dialog) {
+ mWaitingForResult = true;
+ mInput.requestFocus();
+ InputMethodManager inputManager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.showSoftInput(mInput, 0);
+ }
+ });
+
+ return dialog;
+ }
+
+ private void changeFolderName() {
+ final String name = mInput.getText().toString();
+ if (!TextUtils.isEmpty(name)) {
+ // Make sure we have the right folder info
+ mFolderInfo = sFolders.get(mFolderInfo.id);
+ mFolderInfo.title = name;
+ LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
+
+ if (mWorkspaceLoading) {
+ lockAllApps();
+ mModel.startLoader(Launcher.this, false);
+ } else {
+ final FolderIcon folderIcon = (FolderIcon)
+ mWorkspace.getViewForTag(mFolderInfo);
+ if (folderIcon != null) {
+ // TODO: At some point we'll probably want some version of setting
+ // the text for a folder icon.
+ //folderIcon.setText(name);
+ getWorkspace().requestLayout();
+ } else {
+ lockAllApps();
+ mWorkspaceLoading = true;
+ mModel.startLoader(Launcher.this, false);
+ }
+ }
+ }
+ cleanup();
+ }
+
+ private void cleanup() {
+ dismissDialog(DIALOG_RENAME_FOLDER);
+ mWaitingForResult = false;
+ mFolderInfo = null;
+ }
+ }
+
+ // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
+ public boolean isAllAppsVisible() {
+ return (mState == State.APPS_CUSTOMIZE);
+ }
+
+ /**
+ * Helper method for the cameraZoomIn/cameraZoomOut animations
+ * @param view The view being animated
+ * @param state The state that we are moving in or out of (eg. APPS_CUSTOMIZE)
+ * @param scaleFactor The scale factor used for the zoom
+ */
+ private void setPivotsForZoom(View view, float scaleFactor) {
+ view.setPivotX(view.getWidth() / 2.0f);
+ view.setPivotY(view.getHeight() / 2.0f);
+ }
+
+ void updateWallpaperVisibility(boolean visible) {
+ int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
+ int curflags = getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ if (wpflags != curflags) {
+ getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
+ }
+ }
+
+ /**
+ * Things to test when changing the following seven functions.
+ * - Home from workspace
+ * - from center screen
+ * - from other screens
+ * - Home from all apps
+ * - from center screen
+ * - from other screens
+ * - Back from all apps
+ * - from center screen
+ * - from other screens
+ * - Launch app from workspace and quit
+ * - with back
+ * - with home
+ * - Launch app from all apps and quit
+ * - with back
+ * - with home
+ * - Go to a screen that's not the default, then all
+ * apps, and launch and app, and go back
+ * - with back
+ * -with home
+ * - On workspace, long press power and go back
+ * - with back
+ * - with home
+ * - On all apps, long press power and go back
+ * - with back
+ * - with home
+ * - On workspace, power off
+ * - On all apps, power off
+ * - Launch an app and turn off the screen while in that app
+ * - Go back with home key
+ * - Go back with back key TODO: make this not go to workspace
+ * - From all apps
+ * - From workspace
+ * - Enter and exit car mode (becuase it causes an extra configuration changed)
+ * - From all apps
+ * - From the center workspace
+ * - From another workspace
+ */
+
+ /**
+ * Zoom the camera out from the workspace to reveal 'toView'.
+ * Assumes that the view to show is anchored at either the very top or very bottom
+ * of the screen.
+ */
+ private void showAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
+ if (mStateAnimation != null) {
+ mStateAnimation.cancel();
+ mStateAnimation = null;
+ }
+ final Resources res = getResources();
+ final Launcher instance = this;
+
+ final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
+ final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
+ final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+ final View toView = mAppsCustomizeTabHost;
+ final int startDelay =
+ res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
+
+ setPivotsForZoom(toView, scale);
+
+ // Shrink workspaces away if going to AppsCustomize from workspace
+ mWorkspace.changeState(Workspace.State.SMALL, animated);
+
+ if (animated) {
+ final ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
+ scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator());
+ scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ toView.setScaleX(a * scale + b * 1f);
+ toView.setScaleY(a * scale + b * 1f);
+ }
+ });
+
+ toView.setVisibility(View.VISIBLE);
+ toView.setAlpha(0f);
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(fadeDuration);
+ alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
+ alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ // don't need to invalidate because we do so above
+ toView.setAlpha(a * 0f + b * 1f);
+ }
+ });
+ alphaAnim.setStartDelay(startDelay);
+ alphaAnim.start();
+
+ scaleAnim.addListener(new AnimatorListenerAdapter() {
+ boolean animationCancelled = false;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ updateWallpaperVisibility(true);
+ // Prepare the position
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // If we don't set the final scale values here, if this animation is cancelled
+ // it will have the wrong scale value and subsequent cameraPan animations will
+ // not fix that
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ if (toView instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance,
+ scaleAnim, false);
+ }
+
+ if (!springLoaded && !LauncherApplication.isScreenLarge()) {
+ // Hide the workspace scrollbar
+ mWorkspace.hideScrollingIndicator(true);
+ hideDockDivider();
+ }
+ if (!animationCancelled) {
+ updateWallpaperVisibility(false);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ animationCancelled = true;
+ }
+ });
+
+ // toView should appear right at the end of the workspace shrink animation
+ mStateAnimation = new AnimatorSet();
+ mStateAnimation.play(scaleAnim).after(startDelay);
+
+ boolean delayAnim = false;
+ if (toView instanceof LauncherTransitionable) {
+ LauncherTransitionable lt = (LauncherTransitionable) toView;
+ delayAnim = lt.onLauncherTransitionStart(instance, mStateAnimation, false);
+ }
+ // if the anim is delayed, the LauncherTransitionable is responsible for starting it
+ if (!delayAnim) {
+ // TODO: q-- what if this anim is cancelled before being started? or started after
+ // being cancelled?
+ mStateAnimation.start();
+ }
+ } else {
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+ if (toView instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) toView).onLauncherTransitionStart(instance, null, false);
+ ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance, null, false);
+
+ if (!springLoaded && !LauncherApplication.isScreenLarge()) {
+ // Hide the workspace scrollbar
+ mWorkspace.hideScrollingIndicator(true);
+ hideDockDivider();
+ }
+ }
+ updateWallpaperVisibility(false);
+ }
+ }
+
+ /**
+ * Zoom the camera back into the workspace, hiding 'fromView'.
+ * This is the opposite of showAppsCustomizeHelper.
+ * @param animated If true, the transition will be animated.
+ */
+ private void hideAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
+ if (mStateAnimation != null) {
+ mStateAnimation.cancel();
+ mStateAnimation = null;
+ }
+ Resources res = getResources();
+ final Launcher instance = this;
+
+ final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
+ final float scaleFactor = (float)
+ res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+ final View fromView = mAppsCustomizeTabHost;
+
+ setPivotsForZoom(fromView, scaleFactor);
+ updateWallpaperVisibility(true);
+ showHotseat(animated);
+ if (animated) {
+ final float oldScaleX = fromView.getScaleX();
+ final float oldScaleY = fromView.getScaleY();
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
+ scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator());
+ scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ fromView.setScaleX(a * oldScaleX + b * scaleFactor);
+ fromView.setScaleY(a * oldScaleY + b * scaleFactor);
+ }
+ });
+ final ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f);
+ alphaAnim.setDuration(res.getInteger(R.integer.config_appsCustomizeFadeOutTime));
+ alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+ alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ fromView.setAlpha(a * 1f + b * 0f);
+ }
+ });
+ if (fromView instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, alphaAnim,
+ true);
+ }
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateWallpaperVisibility(true);
+ fromView.setVisibility(View.GONE);
+ if (fromView instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance,
+ alphaAnim, true);
+ }
+ }
+ });
+
+ mStateAnimation = new AnimatorSet();
+ mStateAnimation.playTogether(scaleAnim, alphaAnim);
+ mStateAnimation.start();
+ } else {
+ fromView.setVisibility(View.GONE);
+ if (fromView instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, null, true);
+ ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance, null, true);
+ }
+ mWorkspace.hideScrollingIndicator(false);
+ }
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+ mAppsCustomizeTabHost.onTrimMemory();
+ }
+ }
+
+ void showWorkspace(boolean animated) {
+ Resources res = getResources();
+ int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
+
+ mWorkspace.changeState(Workspace.State.NORMAL, animated, stagger);
+ if (mState != State.WORKSPACE) {
+ mWorkspace.setVisibility(View.VISIBLE);
+ hideAppsCustomizeHelper(animated, false);
+
+ // Show the search bar and hotseat
+ mSearchDropTargetBar.showSearchBar(animated);
+ // We only need to animate in the dock divider if we're going from spring loaded mode
+ showDockDivider(animated && mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
+
+ // Set focus to the AppsCustomize button
+ if (mAllAppsButton != null) {
+ mAllAppsButton.requestFocus();
+ }
+ }
+
+ mWorkspace.flashScrollingIndicator(animated);
+
+ // Change the state *after* we've called all the transition code
+ mState = State.WORKSPACE;
+
+ // Resume the auto-advance of widgets
+ mUserPresent = true;
+ updateRunning();
+
+ // send an accessibility event to announce the context change
+ getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+
+ void showAllApps(boolean animated) {
+ if (mState != State.WORKSPACE) return;
+
+ showAppsCustomizeHelper(animated, false);
+ mAppsCustomizeTabHost.requestFocus();
+
+ // Hide the search bar and hotseat
+ mSearchDropTargetBar.hideSearchBar(animated);
+
+ // Change the state *after* we've called all the transition code
+ mState = State.APPS_CUSTOMIZE;
+
+ // Pause the auto-advance of widgets until we are out of AllApps
+ mUserPresent = false;
+ updateRunning();
+ closeFolder();
+
+ // Send an accessibility event to announce the context change
+ getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+
+ void enterSpringLoadedDragMode() {
+ if (mState == State.APPS_CUSTOMIZE) {
+ mWorkspace.changeState(Workspace.State.SPRING_LOADED);
+ hideAppsCustomizeHelper(true, true);
+ hideDockDivider();
+ mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
+ }
+ }
+
+ void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay) {
+ if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (successfulDrop) {
+ // Before we show workspace, hide all apps again because
+ // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
+ // clean up our state transition functions
+ mAppsCustomizeTabHost.setVisibility(View.GONE);
+ mSearchDropTargetBar.showSearchBar(true);
+ showWorkspace(true);
+ } else {
+ exitSpringLoadedDragMode();
+ }
+ }
+ }, (extendedDelay ?
+ EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
+ EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
+ }
+
+ void exitSpringLoadedDragMode() {
+ if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
+ final boolean animated = true;
+ final boolean springLoaded = true;
+ showAppsCustomizeHelper(animated, springLoaded);
+ mState = State.APPS_CUSTOMIZE;
+ }
+ // Otherwise, we are not in spring loaded mode, so don't do anything.
+ }
+
+ void hideDockDivider() {
+ if (mQsbDivider != null && mDockDivider != null) {
+ if (mShowSearchBar) {
+ mQsbDivider.setVisibility(View.INVISIBLE);
+ }
+ mDockDivider.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ void showDockDivider(boolean animated) {
+ if (mQsbDivider != null && mDockDivider != null) {
+ if (mShowSearchBar) {
+ mQsbDivider.setVisibility(View.VISIBLE);
+ }
+ mDockDivider.setVisibility(View.VISIBLE);
+ if (mDividerAnimator != null) {
+ mDividerAnimator.cancel();
+ if (mShowSearchBar) {
+ mQsbDivider.setAlpha(1f);
+ }
+ mDockDivider.setAlpha(1f);
+ mDividerAnimator = null;
+ }
+ if (animated) {
+ mDividerAnimator = new AnimatorSet();
+ if (mShowSearchBar) {
+ mDividerAnimator.playTogether(ObjectAnimator.ofFloat(mQsbDivider, "alpha", 1f),
+ ObjectAnimator.ofFloat(mDockDivider, "alpha", 1f));
+ } else {
+ mDividerAnimator.play(ObjectAnimator.ofFloat(mDockDivider, "alpha", 1f));
+ }
+ mDividerAnimator.setDuration(mSearchDropTargetBar.getTransitionInDuration());
+ mDividerAnimator.start();
+ }
+ }
+ }
+
+ void lockAllApps() {
+ // TODO
+ }
+
+ void unlockAllApps() {
+ // TODO
+ }
+
+ public boolean isAllAppsCustomizeOpen() {
+ return mState == State.APPS_CUSTOMIZE;
+ }
+
+ /**
+ * Shows the hotseat area.
+ */
+ void showHotseat(boolean animated) {
+ if (!LauncherApplication.isScreenLarge()) {
+ if (animated) {
+ int duration = mSearchDropTargetBar.getTransitionInDuration();
+ mHotseat.animate().alpha(1f).setDuration(duration);
+ } else {
+ mHotseat.setAlpha(1f);
+ }
+ }
+ }
+
+ /**
+ * Hides the hotseat area.
+ */
+ void hideHotseat(boolean animated) {
+ if (!LauncherApplication.isScreenLarge()) {
+ if (animated) {
+ int duration = mSearchDropTargetBar.getTransitionOutDuration();
+ mHotseat.animate().alpha(0f).setDuration(duration);
+ } else {
+ mHotseat.setAlpha(0f);
+ }
+ }
+ }
+
+ /**
+ * Add an item from all apps or customize onto the given workspace screen.
+ * If layout is null, add to the current screen.
+ */
+ void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
+ if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
+ showOutOfSpaceMessage();
+ }
+ }
+
+ public int getCurrentOrientation() {
+ return getResources().getConfiguration().orientation;
+ }
+
+ /** Maps the current orientation to an index for referencing orientation correct global icons */
+ private int getCurrentOrientationIndexForGlobalIcons() {
+ // default - 0, landscape - 1
+ switch (getCurrentOrientation()) {
+ case Configuration.ORIENTATION_LANDSCAPE:
+ return 1;
+ default:
+ return 0;
+ }
+ }
+
+ private Drawable getExternalPackageToolbarIcon(ComponentName activityName) {
+ try {
+ PackageManager packageManager = getPackageManager();
+ // Look for the toolbar icon specified in the activity meta-data
+ Bundle metaData = packageManager.getActivityInfo(
+ activityName, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForActivity(activityName);
+ return res.getDrawable(iconResId);
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // This can happen if the activity defines an invalid drawable
+ Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
+ " not found", e);
+ } catch (Resources.NotFoundException nfe) {
+ // This can happen if the activity defines an invalid drawable
+ Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
+ nfe);
+ }
+ return null;
+ }
+
+ // if successful in getting icon, return it; otherwise, set button to use default drawable
+ private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
+ int buttonId, ComponentName activityName, int fallbackDrawableId) {
+ Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
+ Resources r = getResources();
+ int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
+ int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
+
+ TextView button = (TextView) findViewById(buttonId);
+ // If we were unable to find the icon via the meta-data, use a generic one
+ if (toolbarIcon == null) {
+ toolbarIcon = r.getDrawable(fallbackDrawableId);
+ toolbarIcon.setBounds(0, 0, w, h);
+ if (button != null) {
+ button.setCompoundDrawables(toolbarIcon, null, null, null);
+ }
+ return null;
+ } else {
+ toolbarIcon.setBounds(0, 0, w, h);
+ if (button != null) {
+ button.setCompoundDrawables(toolbarIcon, null, null, null);
+ }
+ return toolbarIcon.getConstantState();
+ }
+ }
+
+ // if successful in getting icon, return it; otherwise, set button to use default drawable
+ private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
+ int buttonId, ComponentName activityName, int fallbackDrawableId) {
+ ImageView button = (ImageView) findViewById(buttonId);
+ Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
+
+ if (button != null) {
+ // If we were unable to find the icon via the meta-data, use a
+ // generic one
+ if (toolbarIcon == null) {
+ button.setImageResource(fallbackDrawableId);
+ } else {
+ button.setImageDrawable(toolbarIcon);
+ }
+ }
+
+ return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
+
+ }
+
+ private void updateTextButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
+ TextView button = (TextView) findViewById(buttonId);
+ button.setCompoundDrawables(d.newDrawable(getResources()), null, null, null);
+ }
+
+ private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
+ ImageView button = (ImageView) findViewById(buttonId);
+ button.setImageDrawable(d.newDrawable(getResources()));
+ }
+
+ private void invalidatePressedFocusedStates(View container, View button) {
+ if (container instanceof HolographicLinearLayout) {
+ HolographicLinearLayout layout = (HolographicLinearLayout) container;
+ layout.invalidatePressedFocusedStates();
+ } else if (button instanceof HolographicImageView) {
+ HolographicImageView view = (HolographicImageView) button;
+ view.invalidatePressedFocusedStates();
+ }
+ }
+
+ private boolean updateGlobalSearchIcon() {
+ final View searchButtonContainer = findViewById(R.id.search_button_container);
+ final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
+ final View searchDivider = findViewById(R.id.search_divider);
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName activityName = searchManager.getGlobalSearchActivity();
+ if (activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo);
+ if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
+ if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
+ searchButton.setVisibility(View.VISIBLE);
+ invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+ return true;
+ } else {
+ // We disable both search and voice search when there is no global search provider
+ if (searchDivider != null) searchDivider.setVisibility(View.GONE);
+ if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+ searchButton.setVisibility(View.GONE);
+ voiceButton.setVisibility(View.GONE);
+ return false;
+ }
+ }
+
+ private void updateGlobalSearchIcon(Drawable.ConstantState d) {
+ final View searchButtonContainer = findViewById(R.id.search_button_container);
+ final View searchButton = (ImageView) findViewById(R.id.search_button);
+ updateButtonWithDrawable(R.id.search_button, d);
+ invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+ }
+
+ private boolean updateVoiceSearchIcon(boolean searchVisible) {
+ final View searchDivider = findViewById(R.id.search_divider);
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+
+ // We only show/update the voice search icon if the search icon is enabled as well
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ ComponentName activityName = intent.resolveActivity(getPackageManager());
+ if (searchVisible && activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo);
+ if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
+ voiceButton.setVisibility(View.VISIBLE);
+ invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+ return true;
+ } else {
+ if (searchDivider != null) searchDivider.setVisibility(View.GONE);
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+ voiceButton.setVisibility(View.GONE);
+ return false;
+ }
+ }
+
+ private void updateVoiceSearchIcon(Drawable.ConstantState d) {
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+ updateButtonWithDrawable(R.id.voice_button, d);
+ invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+ }
+
+ /**
+ * Sets the app market icon
+ */
+ private void updateAppMarketIcon() {
+ final View marketButton = findViewById(R.id.market_button);
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
+ // Find the app market activity by resolving an intent.
+ // (If multiple app markets are installed, it will return the ResolverActivity.)
+ ComponentName activityName = intent.resolveActivity(getPackageManager());
+ if (activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ mAppMarketIntent = intent;
+ sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
+ R.id.market_button, activityName, R.drawable.ic_launcher_market_holo);
+ marketButton.setVisibility(View.VISIBLE);
+ } else {
+ // We should hide and disable the view so that we don't try and restore the visibility
+ // of it when we swap between drag & normal states from IconDropTarget subclasses.
+ marketButton.setVisibility(View.GONE);
+ marketButton.setEnabled(false);
+ }
+ }
+
+ private void updateAppMarketIcon(Drawable.ConstantState d) {
+ updateTextButtonWithDrawable(R.id.market_button, d);
+ }
+
+ /**
+ * Displays the shortcut creation dialog and launches, if necessary, the
+ * appropriate activity.
+ */
+ private class CreateShortcut implements DialogInterface.OnClickListener,
+ DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
+ DialogInterface.OnShowListener {
+
+ private AddAdapter mAdapter;
+
+ Dialog createDialog() {
+ mAdapter = new AddAdapter(Launcher.this);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this,
+ AlertDialog.THEME_HOLO_DARK);
+ builder.setAdapter(mAdapter, this);
+
+ AlertDialog dialog = builder.create();
+ dialog.setOnCancelListener(this);
+ dialog.setOnDismissListener(this);
+ dialog.setOnShowListener(this);
+
+ return dialog;
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ mWaitingForResult = false;
+ cleanup();
+ }
+
+ public void onDismiss(DialogInterface dialog) {
+ mWaitingForResult = false;
+ cleanup();
+ }
+
+ private void cleanup() {
+ try {
+ dismissDialog(DIALOG_CREATE_SHORTCUT);
+ } catch (Exception e) {
+ // An exception is thrown if the dialog is not visible, which is fine
+ }
+ }
+
+ /**
+ * Handle the action clicked in the "Add to home" dialog.
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ cleanup();
+
+ AddAdapter.ListItem item = (AddAdapter.ListItem) mAdapter.getItem(which);
+ switch (item.actionTag) {
+ case AddAdapter.ITEM_APPLICATION: {
+ if (mAppsCustomizeTabHost != null) {
+ mAppsCustomizeTabHost.selectAppsTab();
+ }
+ showAllApps(true);
+ break;
+ }
+ case AddAdapter.ITEM_APPWIDGET: {
+ if (mAppsCustomizeTabHost != null) {
+ mAppsCustomizeTabHost.selectWidgetsTab();
+ }
+ showAllApps(true);
+ break;
+ }
+ case AddAdapter.ITEM_WALLPAPER: {
+ startWallpaper();
+ break;
+ }
+ }
+ }
+
+ public void onShow(DialogInterface dialog) {
+ mWaitingForResult = true;
+ }
+ }
+
+ /**
+ * Receives notifications when system dialogs are to be closed.
+ */
+ private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ closeSystemDialogs();
+ }
+ }
+
+ /**
+ * Receives notifications whenever the appwidgets are reset.
+ */
+ private class AppWidgetResetObserver extends ContentObserver {
+ public AppWidgetResetObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onAppWidgetReset();
+ }
+ }
+
+ /**
+ * If the activity is currently paused, signal that we need to re-run the loader
+ * in onResume.
+ *
+ * This needs to be called from incoming places where resources might have been loaded
+ * while we are paused. That is becaues the Configuration might be wrong
+ * when we're not running, and if it comes back to what it was when we
+ * were paused, we are not restarted.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ *
+ * @return true if we are currently paused. The caller might be able to
+ * skip some work in that case since we will come back again.
+ */
+ public boolean setLoadOnResume() {
+ if (mPaused) {
+ Log.i(TAG, "setLoadOnResume");
+ mOnResumeNeedsLoad = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public int getCurrentWorkspaceScreen() {
+ if (mWorkspace != null) {
+ return mWorkspace.getCurrentPage();
+ } else {
+ return SCREEN_COUNT / 2;
+ }
+ }
+
+
+ /**
+ * Refreshes the shortcuts shown on the workspace.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void startBinding() {
+ final Workspace workspace = mWorkspace;
+
+ mWorkspace.clearDropTargets();
+ int count = workspace.getChildCount();
+ for (int i = 0; i < count; i++) {
+ // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
+ final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
+ layoutParent.removeAllViewsInLayout();
+ }
+ mWidgetsToAdvance.clear();
+ if (mHotseat != null) {
+ mHotseat.resetLayout();
+ }
+ }
+
+ /**
+ * Bind the items start-end from the list.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
+ setLoadOnResume();
+
+ final Workspace workspace = mWorkspace;
+ for (int i=start; i<end; i++) {
+ final ItemInfo item = shortcuts.get(i);
+
+ // Short circuit if we are loading dock items for a configuration which has no dock
+ if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+ mHotseat == null) {
+ continue;
+ }
+
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ View shortcut = createShortcut((ShortcutInfo)item);
+ workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
+ item.cellY, 1, 1, false);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+ (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
+ (FolderInfo) item, mIconCache);
+ workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
+ item.cellY, 1, 1, false);
+ break;
+ }
+ }
+ workspace.requestLayout();
+ }
+
+ /**
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindFolders(HashMap<Long, FolderInfo> folders) {
+ setLoadOnResume();
+ sFolders.clear();
+ sFolders.putAll(folders);
+ }
+
+ /**
+ * Add the views for a widget to the workspace.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppWidget(LauncherAppWidgetInfo item) {
+ setLoadOnResume();
+
+ final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bindAppWidget: " + item);
+ }
+ final Workspace workspace = mWorkspace;
+
+ final int appWidgetId = item.appWidgetId;
+ final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
+ }
+
+ item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+
+ item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+ item.hostView.setTag(item);
+
+ workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
+ item.cellY, item.spanX, item.spanY, false);
+
+ addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
+
+ workspace.requestLayout();
+
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+ + (SystemClock.uptimeMillis()-start) + "ms");
+ }
+ }
+
+ /**
+ * Callback saying that there aren't any more items to bind.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void finishBindingItems() {
+ setLoadOnResume();
+
+ if (mSavedState != null) {
+ if (!mWorkspace.hasFocus()) {
+ mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
+ }
+ mSavedState = null;
+ }
+
+ if (mSavedInstanceState != null) {
+ super.onRestoreInstanceState(mSavedInstanceState);
+ mSavedInstanceState = null;
+ }
+
+ mWorkspaceLoading = false;
+
+ // If we received the result of any pending adds while the loader was running (e.g. the
+ // widget configuration forced an orientation change), process them now.
+ for (int i = 0; i < sPendingAddList.size(); i++) {
+ completeAdd(sPendingAddList.get(i));
+ }
+ sPendingAddList.clear();
+
+ // Update the market app icon as necessary (the other icons will be managed in response to
+ // package changes in bindSearchablesChanged()
+ updateAppMarketIcon();
+
+ mWorkspace.post(mBuildLayersRunnable);
+ }
+
+ @Override
+ public void bindSearchablesChanged() {
+ boolean searchVisible = updateGlobalSearchIcon();
+ boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
+ mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+ }
+
+ /**
+ * Add the icons for all apps.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
+ // Remove the progress bar entirely; we could also make it GONE
+ // but better to remove it since we know it's not going to be used
+ View progressBar = mAppsCustomizeTabHost.
+ findViewById(R.id.apps_customize_progress_bar);
+ if (progressBar != null) {
+ ((ViewGroup)progressBar.getParent()).removeView(progressBar);
+ }
+ // We just post the call to setApps so the user sees the progress bar
+ // disappear-- otherwise, it just looks like the progress bar froze
+ // which doesn't look great
+ mAppsCustomizeTabHost.post(new Runnable() {
+ public void run() {
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.setApps(apps);
+ }
+ }
+ });
+ }
+
+ /**
+ * A package was installed.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
+ setLoadOnResume();
+ removeDialog(DIALOG_CREATE_SHORTCUT);
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.addApps(apps);
+ }
+ }
+
+ /**
+ * A package was updated.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
+ setLoadOnResume();
+ removeDialog(DIALOG_CREATE_SHORTCUT);
+ if (mWorkspace != null) {
+ mWorkspace.updateShortcuts(apps);
+ }
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.updateApps(apps);
+ }
+ }
+
+ /**
+ * A package was uninstalled.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) {
+ removeDialog(DIALOG_CREATE_SHORTCUT);
+ if (permanent) {
+ mWorkspace.removeItems(apps);
+ }
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.removeApps(apps);
+ }
+
+ // Notify the drag controller
+ mDragController.onAppsRemoved(apps, this);
+ }
+
+ /**
+ * A number of packages were updated.
+ */
+ public void bindPackagesUpdated() {
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.onPackagesUpdated();
+ }
+ }
+
+ private int mapConfigurationOriActivityInfoOri(int configOri) {
+ final Display d = getWindowManager().getDefaultDisplay();
+ int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
+ switch (d.getRotation()) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ // We are currently in the same basic orientation as the natural orientation
+ naturalOri = configOri;
+ break;
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ // We are currently in the other basic orientation to the natural orientation
+ naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
+ Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ int[] oriMap = {
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+ };
+ // Since the map starts at portrait, we need to offset if this device's natural orientation
+ // is landscape.
+ int indexOffset = 0;
+ if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
+ indexOffset = 1;
+ }
+ return oriMap[(d.getRotation() + indexOffset) % 4];
+ }
+
+ public void lockScreenOrientationOnLargeUI() {
+ if (LauncherApplication.isScreenLarge()) {
+ setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
+ .getConfiguration().orientation));
+ }
+ }
+ public void unlockScreenOrientationOnLargeUI() {
+ if (LauncherApplication.isScreenLarge()) {
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ }, mRestoreScreenOrientationDelay);
+ }
+ }
+
+ /* Cling related */
+ private boolean isClingsEnabled() {
+ // disable clings when running in a test harness
+ if(ActivityManager.isRunningInTestHarness()) return false;
+
+ return true;
+ }
+ private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) {
+ Cling cling = (Cling) findViewById(clingId);
+ if (cling != null) {
+ cling.init(this, positionData);
+ cling.setVisibility(View.VISIBLE);
+ cling.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (animate) {
+ cling.buildLayer();
+ cling.setAlpha(0f);
+ cling.animate()
+ .alpha(1f)
+ .setInterpolator(new AccelerateInterpolator())
+ .setDuration(SHOW_CLING_DURATION)
+ .setStartDelay(delay)
+ .start();
+ } else {
+ cling.setAlpha(1f);
+ }
+ }
+ return cling;
+ }
+ private void dismissCling(final Cling cling, final String flag, int duration) {
+ if (cling != null) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(cling, "alpha", 0f);
+ anim.setDuration(duration);
+ anim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ cling.setVisibility(View.GONE);
+ cling.cleanup();
+ SharedPreferences prefs =
+ getSharedPreferences(PreferencesProvider.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(flag, true);
+ editor.commit();
+ };
+ });
+ anim.start();
+ }
+ }
+ private void removeCling(int id) {
+ final View cling = findViewById(id);
+ if (cling != null) {
+ final ViewGroup parent = (ViewGroup) cling.getParent();
+ parent.post(new Runnable() {
+ @Override
+ public void run() {
+ parent.removeView(cling);
+ }
+ });
+ }
+ }
+ public void showFirstRunWorkspaceCling() {
+ // Enable the clings only if they have not been dismissed before
+ SharedPreferences prefs =
+ getSharedPreferences(PreferencesProvider.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ if (isClingsEnabled() && !prefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
+ initCling(R.id.workspace_cling, null, false, 0);
+ } else {
+ removeCling(R.id.workspace_cling);
+ }
+ }
+ public void showFirstRunAllAppsCling(int[] position) {
+ // Enable the clings only if they have not been dismissed before
+ SharedPreferences prefs =
+ getSharedPreferences(PreferencesProvider.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ if (isClingsEnabled() && !prefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
+ initCling(R.id.all_apps_cling, position, true, 0);
+ } else {
+ removeCling(R.id.all_apps_cling);
+ }
+ }
+ public Cling showFirstRunFoldersCling() {
+ // Enable the clings only if they have not been dismissed before
+ SharedPreferences prefs =
+ getSharedPreferences(PreferencesProvider.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ Cling cling = null;
+ if (isClingsEnabled() && !prefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
+ cling = initCling(R.id.folder_cling, null, true, 0);
+ } else {
+ removeCling(R.id.folder_cling);
+ }
+ return cling;
+ }
+ public boolean isFolderClingVisible() {
+ Cling cling = (Cling) findViewById(R.id.folder_cling);
+ if (cling != null) {
+ return cling.getVisibility() == View.VISIBLE;
+ }
+ return false;
+ }
+ public void dismissWorkspaceCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.workspace_cling);
+ dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
+ }
+ public void dismissAllAppsCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.all_apps_cling);
+ dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
+ }
+ public void dismissFolderCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.folder_cling);
+ dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
+ }
+
+ public boolean preferencesChanged() {
+ SharedPreferences prefs =
+ getSharedPreferences(PreferencesProvider.PREFERENCES_KEY, Context.MODE_PRIVATE);
+ boolean preferencesChanged = prefs.getBoolean(PreferencesProvider.PREFERENCES_CHANGED, false);
+ if (preferencesChanged) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(PreferencesProvider.PREFERENCES_CHANGED, false);
+ editor.commit();
+ }
+ return preferencesChanged;
+ }
+
+ /**
+ * Prints out out state for debugging.
+ */
+ public void dumpState() {
+ Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
+ Log.d(TAG, "mSavedState=" + mSavedState);
+ Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
+ Log.d(TAG, "mRestoring=" + mRestoring);
+ Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
+ Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
+ Log.d(TAG, "sFolders.size=" + sFolders.size());
+ mModel.dumpState();
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.dumpState();
+ }
+ Log.d(TAG, "END launcher2 dump state");
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ writer.println(" ");
+ writer.println("Debug logs: ");
+ for (int i = 0; i < sDumpLogs.size(); i++) {
+ writer.println(" " + sDumpLogs.get(i));
+ }
+ }
+}
+
+interface LauncherTransitionable {
+ // return true if the callee will take care of start the animation by itself
+ boolean onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace);
+ void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace);
+}