blob: 12d57ac1905d0bc91b83f78f2917bf607c243c6f [file] [log] [blame]
/*
* 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.android.launcher2;
import android.accounts.Account;
import android.accounts.AccountManager;
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.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
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.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
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.ViewTreeObserver.OnGlobalLayoutListener;
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.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.common.Search;
import com.android.launcher.R;
import com.android.launcher2.DropTarget.DragObject;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 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;
static final boolean DEBUG_STRICT_MODE = false;
static final boolean DEBUG_RESUME_TIME = 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_HELP = MENU_SYSTEM_SETTINGS + 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;
private static final int REQUEST_BIND_APPWIDGET = 11;
static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
static final int SCREEN_COUNT = 5;
static final int DEFAULT_SCREEN = 2;
private static final String PREFERENCES = "launcher.preferences";
// To turn on these properties, type
// adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
// The Intent extra that defines whether to ignore the launch animation
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
"com.android.launcher.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
// 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";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
"com.android.launcher.toolbar_search_icon";
private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
"com.android.launcher.toolbar_voice_search_icon";
/** The different states that Launcher can be in. */
private enum State { NONE, 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;
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 10;
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 View mLauncherView;
private DragLayer mDragLayer;
private DragController mDragController;
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private ItemInfo mPendingAddInfo = new ItemInfo();
private AppWidgetProviderInfo mPendingAddWidgetInfo;
private int mPendingAddWidgetId = -1;
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;
// We set the state in both onCreate and then onNewIntent in some cases, which causes both
// scroll issues (because the workspace may not have been measured yet) and extra work.
// Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
private State mOnResumeState = State.NONE;
private SpannableStringBuilder mDefaultKeySsb = null;
private boolean mWorkspaceLoading = true;
private boolean mPaused = true;
private boolean mRestoring;
private boolean mWaitingForResult;
private boolean mOnResumeNeedsLoad;
private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
// Keep track of whether the user has left launcher
private static boolean sPausedFromUserAction = false;
private Bundle mSavedInstanceState;
private LauncherModel mModel;
private IconCache mIconCache;
private boolean mUserPresent = true;
private boolean mVisible = false;
private boolean mAttached = false;
private enum HostDevice { UNKNOWN, EMULATOR, REAL_DEVICE};
private HostDevice mHostDevice = HostDevice.UNKNOWN;
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];
private Drawable mWorkspaceBackgroundDrawable;
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
static final ArrayList<String> sDumpLogs = new ArrayList<String>();
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
private SharedPreferences mSharedPrefs;
// Holds the page that we need to animate to, and the icon views that we need to animate up
// when we scroll to that page on resume.
private int mNewShortcutAnimatePage = -1;
private ArrayList<View> mNewShortcutAnimateViews = new ArrayList<View>();
private ImageView mFolderIconImageView;
private Bitmap mFolderIconBitmap;
private Canvas mFolderIconCanvas;
private Rect mRectForFolderAnimation = new Rect();
private BubbleTextView mWaitingForResume;
private HideFromAccessibilityHelper mHideFromAccessibilityHelper
= new HideFromAccessibilityHelper();
private Runnable mBuildLayersRunnable = new Runnable() {
public void run() {
if (mWorkspace != null) {
mWorkspace.buildPageHardwareLayers();
}
}
};
private static ArrayList<PendingAddArguments> sPendingAddList
= new ArrayList<PendingAddArguments>();
private static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
private static class PendingAddArguments {
int requestCode;
Intent intent;
long container;
int screen;
int cellX;
int cellY;
}
private static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);
LauncherApplication app = ((LauncherApplication)getApplication());
mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
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();
// If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
// this also ensures that any synchronous binding below doesn't re-trigger another
// LauncherModel load.
mPaused = false;
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(
LauncherModel.getSortedWidgetsAndShortcuts(this));
}
if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}
if (!mRestoring) {
if (sPausedFromUserAction) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(true, -1);
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
mModel.startLoader(true, mWorkspace.getCurrentPage());
}
}
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);
updateGlobalIcons();
// On large interfaces, we want the screen to auto-rotate based on the current orientation
unlockScreenOrientation(true);
}
private String getSystemPropertyFromShell(String propertyName) {
if (propertyName == null || propertyName.trim().equals("")) return "";
String propertyValue = "";
try {
java.lang.Process process =
new ProcessBuilder("/system/bin/getprop", propertyName).start();
// try-with-resources
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
process.getInputStream()))) {
String line = bufferedReader.readLine();
if (line != null) propertyValue = line.trim();
}
finally {
process.destroy();
}
} catch (java.io.IOException e) {
Log.e(TAG, "Couldn't find the property value for '" + propertyName + "'");
}
return propertyValue;
}
protected void onUserLeaveHint() {
super.onUserLeaveHint();
sPausedFromUserAction = true;
}
private void updateGlobalIcons() {
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]);
}
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
}
}
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;
}
boolean isDraggingEnabled() {
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
// that is subsequently removed from the workspace in startBinding().
return !mModel.isLoadingWorkspace();
}
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_CREATE_APPWIDGET:
int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
completeAddAppWidget(appWidgetId, args.container, args.screen, null, null);
result = true;
break;
case REQUEST_PICK_WALLPAPER:
// We just wanted the activity result here so we can clear mWaitingForResult
break;
}
// Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
// if you turned the screen off and then back while in All Apps, Launcher would not
// return to the workspace. Clearing mAddInfo.container here fixes this issue
resetAddInfo();
return result;
}
@Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
int pendingAddWidgetId = mPendingAddWidgetId;
mPendingAddWidgetId = -1;
if (requestCode == REQUEST_BIND_APPWIDGET) {
int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
}
return;
}
boolean delayExitSpringLoadedMode = false;
boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET);
mWaitingForResult = false;
// We have special handling for widgets
if (isWidgetDrop) {
final int appWidgetId;
int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
: -1;
if (widgetId < 0) {
appWidgetId = pendingAddWidgetId;
} else {
appWidgetId = widgetId;
}
if (appWidgetId < 0) {
Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
"widget configuration activity.");
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
} else {
completeTwoStageWidgetDrop(resultCode, appWidgetId);
}
return;
}
// 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 (isWorkspaceLocked()) {
sPendingAddList.add(args);
} else {
delayExitSpringLoadedMode = completeAdd(args);
}
}
mDragLayer.clearAnimatedView();
// Exit spring loaded mode if necessary after cancelling the configuration of a widget
exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
null);
}
private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
CellLayout cellLayout =
(CellLayout) mWorkspace.getChildAt(mPendingAddInfo.screen);
Runnable onCompleteRunnable = null;
int animationType = 0;
AppWidgetHostView boundWidget = null;
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
mPendingAddWidgetInfo);
boundWidget = layout;
onCompleteRunnable = new Runnable() {
@Override
public void run() {
completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
mPendingAddInfo.screen, layout, null);
exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
null);
}
};
} else if (resultCode == RESULT_CANCELED) {
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
onCompleteRunnable = new Runnable() {
@Override
public void run() {
exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
null);
}
};
}
if (mDragLayer.getAnimatedView() != null) {
mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
(DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
animationType, boundWidget, true);
} else {
// The animated view may be null in the case of a rotation during widget configuration
onCompleteRunnable.run();
}
}
@Override
protected void onStop() {
super.onStop();
FirstFrameAnimatorHelper.setIsVisible(false);
}
@Override
protected void onStart() {
super.onStart();
FirstFrameAnimatorHelper.setIsVisible(true);
}
@Override
protected void onResume() {
long startTime = 0;
if (DEBUG_RESUME_TIME) {
startTime = System.currentTimeMillis();
}
super.onResume();
// Restore the previous launcher state
if (mOnResumeState == State.WORKSPACE) {
showWorkspace(false);
} else if (mOnResumeState == State.APPS_CUSTOMIZE) {
showAllApps(false);
}
mOnResumeState = State.NONE;
// Background was set to gradient in onPause(), restore to black if in all apps.
setWorkspaceBackground(mState == State.WORKSPACE);
// Process any items that were added while Launcher was away
InstallShortcutReceiver.flushInstallQueue(this);
mPaused = false;
sPausedFromUserAction = false;
if (mRestoring || mOnResumeNeedsLoad) {
mWorkspaceLoading = true;
mModel.startLoader(true, -1);
mRestoring = false;
mOnResumeNeedsLoad = false;
}
if (mOnResumeCallbacks.size() > 0) {
// We might have postponed some bind calls until onResume (see waitUntilResume) --
// execute them here
long startTimeCallbacks = 0;
if (DEBUG_RESUME_TIME) {
startTimeCallbacks = System.currentTimeMillis();
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setBulkBind(true);
}
for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
mOnResumeCallbacks.get(i).run();
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setBulkBind(false);
}
mOnResumeCallbacks.clear();
if (DEBUG_RESUME_TIME) {
Log.d(TAG, "Time spent processing callbacks in onResume: " +
(System.currentTimeMillis() - startTimeCallbacks));
}
}
// Reset the pressed state of icons that were locked in the press state while activities
// were launching
if (mWaitingForResume != null) {
// Resets the previous workspace icon press state
mWaitingForResume.setStayPressed(false);
}
if (mAppsCustomizeContent != null) {
// Resets the previous all apps icon press state
mAppsCustomizeContent.resetDrawableState();
}
// It is possible that widgets can receive updates while launcher is not in the foreground.
// Consequently, the widgets will be inflated in the orientation of the foreground activity
// (framework issue). On resuming, we ensure that any widgets are inflated for the current
// orientation.
getWorkspace().reinflateWidgetsIfNecessary();
// Again, as with the above scenario, it's possible that one or more of the global icons
// were updated in the wrong orientation.
updateGlobalIcons();
if (DEBUG_RESUME_TIME) {
Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
}
}
@Override
protected void onPause() {
// NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
// to be consistent. So re-enable the flag here, and we will re-disable it as necessary
// when Launcher resumes and we are still in AllApps.
updateWallpaperVisibility(true);
super.onPause();
mPaused = true;
mDragController.cancelDrag();
mDragController.resetLastGestureUpTime();
}
@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) {
mOnResumeState = State.APPS_CUSTOMIZE;
}
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);
mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
mWaitingForResult = true;
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) {
mAppsCustomizeTabHost.setContentTypeImmediate(
mAppsCustomizeTabHost.getContentTypeForTabTag(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;
mLauncherView = findViewById(R.id.launcher);
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
mQsbDivider = findViewById(R.id.qsb_divider);
mDockDivider = findViewById(R.id.dock_divider);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
// 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);
// Setup AppsCustomize
mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
mAppsCustomizeContent = (AppsCustomizePagedView)
mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
mAppsCustomizeContent.setup(this, dragController);
// 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(isHotseatLayout(layout));
return;
}
final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data,
android.os.Process.myUserHandle(), this);
if (info != null) {
// Necessary flags are added when the activity is launched via
// LauncherApps
info.setActivity(data);
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, 0,
true, null,null)) {
return;
}
DragObject dragObject = new DragObject();
dragObject.dragInfo = info;
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, 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(isHotseatLayout(layout));
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());
}
}
static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
int minHeight) {
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, 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(context.getResources(), requiredWidth, requiredHeight, null);
}
static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
}
static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
}
static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
return getSpanForWidget(context, info.componentName, info.info.minWidth,
info.info.minHeight);
}
static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
return getSpanForWidget(context, info.componentName, info.info.minResizeWidth,
info.info.minResizeHeight);
}
/**
* 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,
AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
if (appWidgetInfo == null) {
appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
}
// Calculate the grid spans needed to fit this widget
CellLayout layout = getCellLayout(container, screen);
int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
int[] spanXY = getSpanForWidget(this, appWidgetInfo);
// 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;
int[] finalSpan = new int[2];
boolean foundCellSpan = false;
if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
cellXY[0] = mPendingAddInfo.cellX;
cellXY[1] = mPendingAddInfo.cellY;
spanXY[0] = mPendingAddInfo.spanX;
spanXY[1] = mPendingAddInfo.spanY;
foundCellSpan = true;
} else if (touchXY != null) {
// when dragging and dropping, just find the closest free spot
int[] result = layout.findNearestVacantArea(
touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
spanXY[1], cellXY, finalSpan);
spanXY[0] = finalSpan[0];
spanXY[1] = finalSpan[1];
foundCellSpan = (result != null);
} else {
foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[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(isHotseatLayout(layout));
return;
}
// Build Launcher-specific widget info and save to database
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
appWidgetInfo.provider);
launcherInfo.spanX = spanXY[0];
launcherInfo.spanY = spanXY[1];
launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
launcherInfo.user = appWidgetInfo.getProfile();
LauncherModel.addItemToDatabase(this, launcherInfo,
container, screen, cellXY[0], cellXY[1], false);
if (!mRestoring) {
if (hostView == null) {
// Perform actual inflation because we're live
launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
} else {
// The AppWidgetHostView has already been inflated and instantiated
launcherInfo.hostView = hostView;
}
launcherInfo.hostView.setTag(launcherInfo);
launcherInfo.hostView.setVisibility(View.VISIBLE);
launcherInfo.notifyWidgetSizeChanged(this);
mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
}
resetAddInfo();
}
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();
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
|| Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
getModel().forceReload();
}
}
};
@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);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
mAttached = true;
mVisible = true;
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
if (mAttached) {
unregisterReceiver(mReceiver);
mAttached = false;
}
updateRunning();
}
public void onWindowVisibilityChanged(int visibility) {
mVisible = visibility == View.VISIBLE;
updateRunning();
// The following code used to be in onResume, but it turns out onResume is called when
// you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
// is a more appropriate event to handle
if (mVisible) {
mAppsCustomizeTabHost.onWindowVisible();
if (!mWorkspaceLoading) {
final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
// 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.
observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
private boolean mStarted = false;
public void onDraw() {
if (mStarted) return;
mStarted = true;
// We delay the layer building a bit in order to give
// other message processing a time to run. In particular
// this avoids a delay in hiding the IME if it was
// currently shown, because doing that may involve
// some communication back with the app.
mWorkspace.postDelayed(mBuildLayersRunnable, 500);
final ViewTreeObserver.OnDrawListener listener = this;
mWorkspace.post(new Runnable() {
public void run() {
if (mWorkspace != null &&
mWorkspace.getViewTreeObserver() != null) {
mWorkspace.getViewTreeObserver().
removeOnDrawListener(listener);
}
}
});
return;
}
});
}
// When Launcher comes back to foreground, a different Activity might be responsible for
// the app market intent, so refresh the icon
updateAppMarketIcon();
clearTypedText();
}
}
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(boolean isHotseatLayout) {
int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
}
public LauncherAppWidgetHost getAppWidgetHost() {
return mAppWidgetHost;
}
public LauncherModel getModel() {
return mModel;
}
void closeSystemDialogs() {
getWindow().closeAllPanels();
// Whatever we were doing is hereby canceled.
mWaitingForResult = false;
}
@Override
protected void onNewIntent(Intent intent) {
long startTime = 0;
if (DEBUG_RESUME_TIME) {
startTime = System.currentTimeMillis();
}
super.onNewIntent(intent);
// Close the menu
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
// also will cancel mWaitingForResult.
closeSystemDialogs();
final boolean alreadyOnHome =
((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
Runnable processIntent = new Runnable() {
public void run() {
if (mWorkspace == null) {
// Can be cases where mWorkspace is null, this prevents a NPE
return;
}
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();
// If we are already on home, then just animate back to the workspace,
// otherwise, just wait until onResume to set the state back to Workspace
if (alreadyOnHome) {
showWorkspace(true);
} else {
mOnResumeState = State.WORKSPACE;
}
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();
}
}
};
if (alreadyOnHome && !mWorkspace.hasWindowFocus()) {
// Delay processing of the intent to allow the status bar animation to finish
// first in order to avoid janky animations.
mWorkspace.postDelayed(processIntent, 350);
} else {
// Process the intent immediately.
processIntent.run();
}
}
if (DEBUG_RESUME_TIME) {
Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
}
}
@Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
for (int page: mSynchronouslyBoundPages) {
mWorkspace.restoreInstanceStateForChild(page);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
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);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
}
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();
// Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
// to prevent leaking Launcher activities on orientation change.
if (mModel != null) {
mModel.unbindItemInfosAndClearQueuedBindRunnables();
}
getContentResolver().unregisterContentObserver(mWidgetObserver);
unregisterReceiver(mCloseSystemDialogsReceiver);
mDragLayer.clearAllResizeFrames();
((ViewGroup) mWorkspace.getParent()).removeAllViews();
mWorkspace.removeAllViews();
mWorkspace = null;
mDragController = null;
LauncherAnimUtils.onDestroyActivity();
}
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 = new Rect();
if (mSearchDropTargetBar != null) {
sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
}
startGlobalSearch(initialQuery, selectInitialQuery,
appSearchData, sourceBounds);
}
/**
* Starts the global search activity. This code is a copied from SearchManager
*/
public void startGlobalSearch(String initialQuery,
boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
final SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
if (globalSearchActivity == null) {
Log.w(TAG, "No global search activity found.");
return;
}
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(globalSearchActivity);
// Make sure that we have a Bundle to put source in
if (appSearchData == null) {
appSearchData = new Bundle();
} else {
appSearchData = new Bundle(appSearchData);
}
// Set source to package name of app that starts global search, if not set already.
if (!appSearchData.containsKey("source")) {
appSearchData.putString("source", getPackageName());
}
intent.putExtra(SearchManager.APP_DATA, appSearchData);
if (!TextUtils.isEmpty(initialQuery)) {
intent.putExtra(SearchManager.QUERY, initialQuery);
}
if (selectInitialQuery) {
intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
}
intent.setSourceBounds(sourceBounds);
try {
startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
}
}
@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);
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');
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
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.minSpanX = mPendingAddInfo.minSpanY = -1;
mPendingAddInfo.dropPos = null;
}
void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
AppWidgetProviderInfo appWidgetInfo) {
if (appWidgetInfo.configure != null) {
mPendingAddWidgetInfo = appWidgetInfo;
mPendingAddWidgetId = appWidgetId;
// Launch over to configure widget, if needed
startAppWidgetConfigureActivitySafely(appWidgetId);
} else {
// Otherwise just add it
completeAddAppWidget(appWidgetId, info.container, info.screen, boundWidget,
appWidgetInfo);
// Exit spring loaded mode if necessary after adding the widget
exitSpringLoadedDragModeDelayed(true, false, null);
}
}
/**
* 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[] span, int[] loc) {
resetAddInfo();
mPendingAddInfo.container = info.container = container;
mPendingAddInfo.screen = info.screen = screen;
mPendingAddInfo.dropPos = loc;
mPendingAddInfo.minSpanX = info.minSpanX;
mPendingAddInfo.minSpanY = info.minSpanY;
if (cell != null) {
mPendingAddInfo.cellX = cell[0];
mPendingAddInfo.cellY = cell[1];
}
if (span != null) {
mPendingAddInfo.spanX = span[0];
mPendingAddInfo.spanY = span[1];
}
AppWidgetHostView hostView = info.boundWidget;
int appWidgetId;
if (hostView != null) {
appWidgetId = hostView.getAppWidgetId();
addAppWidgetImpl(appWidgetId, info, hostView, info.info);
} else {
// In this case, we either need to start an activity to get permission to bind
// the widget, or we need to start an activity to configure the widget, or both.
appWidgetId = getAppWidgetHost().allocateAppWidgetId();
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
info.info.getProfile(), info.componentName, info.bindOptions);
if (success) {
addAppWidgetImpl(appWidgetId, info, null, info.info);
} else {
mPendingAddWidgetInfo = info.info;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
info.info.getProfile());
// TODO: we need to make sure that this accounts for the options bundle.
// intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
}
}
}
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 (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
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 (isAllAppsVisible()) {
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();
}
}
/**
* 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.isFinishedSwitchingState()) {
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(v, 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 (isAllAppsVisible()) {
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);
try {
final SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
ComponentName activityName = searchManager.getGlobalSearchActivity();
Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (activityName != null) {
intent.setPackage(activityName.getPackageName());
}
startActivity(null, intent, "onClickVoiceButton");
} catch (ActivityNotFoundException e) {
Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivitySafely(null, intent, "onClickVoiceButton");
}
}
/**
* 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(v, mAppMarketIntent, "app market");
} else {
Log.e(TAG, "Invalid app market intent.");
}
}
void startApplicationDetailsActivity(ComponentName componentName, UserHandle user) {
LauncherApps launcherApps = (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE);
try {
launcherApps.startAppDetailsActivity(componentName, user, null, null);
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Launcher does not have permission to launch settings");
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch settings");
}
}
void startApplicationUninstallActivity(ApplicationInfo appInfo, UserHandle user) {
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);
if (user != null) {
intent.putExtra(Intent.EXTRA_USER, user);
}
startActivity(intent);
}
}
boolean startActivity(View v, Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
// Only launch using the new animation if the shortcut has not opted out (this is a
// private contract between launcher and may be ignored in the future).
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
UserHandle user = (UserHandle) intent.getParcelableExtra(ApplicationInfo.EXTRA_PROFILE);
LauncherApps launcherApps = (LauncherApps)
this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
if (useLaunchAnimation) {
ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
v.getMeasuredWidth(), v.getMeasuredHeight());
if (user == null || user.equals(android.os.Process.myUserHandle())) {
// Could be launching some bookkeeping activity
startActivity(intent, opts.toBundle());
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(),
opts.toBundle());
}
} else {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent);
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(), null);
}
}
return true;
} 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;
}
boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
try {
success = startActivity(v, intent, tag);
} 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);
}
return success;
}
void startAppWidgetConfigureActivitySafely(int appWidgetId) {
try {
mAppWidgetHost.startAppWidgetConfigureActivityForResult(this, appWidgetId, 0,
REQUEST_CREATE_APPWIDGET, null);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
}
}
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.getFolderInfo();
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 && !folderIcon.getFolder().isDestroyed()) {
// 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);
}
}
}
}
/**
* This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
* in the DragLayer in the exact absolute location of the original FolderIcon.
*/
private void copyFolderIconToImage(FolderIcon fi) {
final int width = fi.getMeasuredWidth();
final int height = fi.getMeasuredHeight();
// Lazy load ImageView, Bitmap and Canvas
if (mFolderIconImageView == null) {
mFolderIconImageView = new ImageView(this);
}
if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
mFolderIconBitmap.getHeight() != height) {
mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mFolderIconCanvas = new Canvas(mFolderIconBitmap);
}
DragLayer.LayoutParams lp;
if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
} else {
lp = new DragLayer.LayoutParams(width, height);
}
// The layout from which the folder is being opened may be scaled, adjust the starting
// view size by this scale factor.
float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
lp.customPosition = true;
lp.x = mRectForFolderAnimation.left;
lp.y = mRectForFolderAnimation.top;
lp.width = (int) (scale * width);
lp.height = (int) (scale * height);
mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
fi.draw(mFolderIconCanvas);
mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
if (fi.getFolder() != null) {
mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
}
// Just in case this image view is still in the drag layer from a previous animation,
// we remove it and re-add it.
if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
mDragLayer.removeView(mFolderIconImageView);
}
mDragLayer.addView(mFolderIconImageView, lp);
if (fi.getFolder() != null) {
fi.getFolder().bringToFront();
}
}
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);
}
// Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
copyFolderIconToImage(fi);
fi.setVisibility(View.INVISIBLE);
ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
scaleX, scaleY);
oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
oa.start();
}
private void shrinkAndFadeInFolderIcon(final 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);
final CellLayout cl = (CellLayout) fi.getParent().getParent();
// We remove and re-draw the FolderIcon in-case it has changed
mDragLayer.removeView(mFolderIconImageView);
copyFolderIconToImage(fi);
ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
scaleX, scaleY);
oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (cl != null) {
cl.clearFolderLeaveBehind();
// Remove the ImageView copy of the FolderIcon and make the original visible.
mDragLayer.removeView(mFolderIconImageView);
fi.setVisibility(View.VISIBLE);
}
}
});
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.getFolder();
FolderInfo info = folder.mInfo;
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();
growAndFadeOutFolderIcon(folderIcon);
// Notify the accessibility manager that this folder "window" has appeared and occluded
// the workspace items
folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
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();
// Notify the accessibility manager that this folder "window" has disappeard and no
// longer occludeds the workspace items
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
public boolean onLongClick(View v) {
if (!isDraggingEnabled()) return false;
if (isWorkspaceLocked()) return false;
if (mState != State.WORKSPACE) 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;
}
// Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
@Override
public boolean isAllAppsVisible() {
return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
}
@Override
public boolean isAllAppsButtonRank(int rank) {
return mHotseat.isAllAppsButtonRank(rank);
}
/**
* Helper method for the cameraZoomIn/cameraZoomOut animations
* @param view The view being animated
* @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 disableWallpaperIfInAllApps() {
// Only disable it if we are in all apps
if (isAllAppsVisible()) {
if (mAppsCustomizeTabHost != null &&
!mAppsCustomizeTabHost.isTransitioning()) {
updateWallpaperVisibility(false);
}
}
}
private void setWorkspaceBackground(boolean workspace) {
mLauncherView.setBackground(workspace ?
mWorkspaceBackgroundDrawable : null);
}
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);
}
setWorkspaceBackground(visible);
}
private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
if (v instanceof LauncherTransitionable) {
((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
}
}
private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
if (v instanceof LauncherTransitionable) {
((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
}
// Update the workspace transition step as well
dispatchOnLauncherTransitionStep(v, 0f);
}
private void dispatchOnLauncherTransitionStep(View v, float t) {
if (v instanceof LauncherTransitionable) {
((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
}
}
private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
if (v instanceof LauncherTransitionable) {
((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
}
// Update the workspace transition step as well
dispatchOnLauncherTransitionStep(v, 1f);
}
/**
* 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(final boolean animated, final boolean springLoaded) {
if (mStateAnimation != null) {
mStateAnimation.setDuration(0);
mStateAnimation.cancel();
mStateAnimation = null;
}
final Resources res = getResources();
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 fromView = mWorkspace;
final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
final int startDelay =
res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
setPivotsForZoom(toView, scale);
// Shrink workspaces away if going to AppsCustomize from workspace
Animator workspaceAnim =
mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
if (animated) {
toView.setScaleX(scale);
toView.setScaleY(scale);
final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
scaleAnim.
scaleX(1f).scaleY(1f).
setDuration(duration).
setInterpolator(new Workspace.ZoomOutInterpolator());
toView.setVisibility(View.VISIBLE);
toView.setAlpha(0f);
final ObjectAnimator alphaAnim = LauncherAnimUtils
.ofFloat(toView, "alpha", 0f, 1f)
.setDuration(fadeDuration);
alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (animation == null) {
throw new RuntimeException("animation is null");
}
float t = (Float) animation.getAnimatedValue();
dispatchOnLauncherTransitionStep(fromView, t);
dispatchOnLauncherTransitionStep(toView, t);
}
});
// toView should appear right at the end of the workspace shrink
// animation
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
mStateAnimation.play(scaleAnim).after(startDelay);
mStateAnimation.play(alphaAnim).after(startDelay);
mStateAnimation.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) {
dispatchOnLauncherTransitionEnd(fromView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
if (mWorkspace != null && !springLoaded && !LauncherApplication.isScreenLarge()) {
// Hide the workspace scrollbar
mWorkspace.hideScrollingIndicator(true);
hideDockDivider();
}
if (!animationCancelled) {
updateWallpaperVisibility(false);
}
// Hide the search bar
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.hideSearchBar(false);
}
}
@Override
public void onAnimationCancel(Animator animation) {
animationCancelled = true;
}
});
if (workspaceAnim != null) {
mStateAnimation.play(workspaceAnim);
}
boolean delayAnim = false;
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
dispatchOnLauncherTransitionPrepare(toView, animated, false);
// If any of the objects being animated haven't been measured/laid out
// yet, delay the animation until we get a layout pass
if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
(mWorkspace.getMeasuredWidth() == 0) ||
(toView.getMeasuredWidth() == 0)) {
delayAnim = true;
}
final AnimatorSet stateAnimation = mStateAnimation;
final Runnable startAnimRunnable = new Runnable() {
public void run() {
// Check that mStateAnimation hasn't changed while
// we waited for a layout/draw pass
if (mStateAnimation != stateAnimation)
return;
setPivotsForZoom(toView, scale);
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
}
};
if (delayAnim) {
final ViewTreeObserver observer = toView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
startAnimRunnable.run();
toView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
} else {
startAnimRunnable.run();
}
} else {
toView.setTranslationX(0.0f);
toView.setTranslationY(0.0f);
toView.setScaleX(1.0f);
toView.setScaleY(1.0f);
toView.setVisibility(View.VISIBLE);
toView.bringToFront();
if (!springLoaded && !LauncherApplication.isScreenLarge()) {
// Hide the workspace scrollbar
mWorkspace.hideScrollingIndicator(true);
hideDockDivider();
// Hide the search bar
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.hideSearchBar(false);
}
}
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionEnd(fromView, animated, false);
dispatchOnLauncherTransitionPrepare(toView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
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(State toState, final boolean animated,
final boolean springLoaded, final Runnable onCompleteRunnable) {
if (mStateAnimation != null) {
mStateAnimation.setDuration(0);
mStateAnimation.cancel();
mStateAnimation = null;
}
Resources res = getResources();
final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
final int fadeOutDuration =
res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
final float scaleFactor = (float)
res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
final View fromView = mAppsCustomizeTabHost;
final View toView = mWorkspace;
Animator workspaceAnim = null;
if (toState == State.WORKSPACE) {
int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
workspaceAnim = mWorkspace.getChangeStateAnimation(
Workspace.State.NORMAL, animated, stagger);
} else if (toState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
workspaceAnim = mWorkspace.getChangeStateAnimation(
Workspace.State.SPRING_LOADED, animated);
}
setPivotsForZoom(fromView, scaleFactor);
updateWallpaperVisibility(true);
showHotseat(animated);
if (animated) {
final LauncherViewPropertyAnimator scaleAnim =
new LauncherViewPropertyAnimator(fromView);
scaleAnim.
scaleX(scaleFactor).scaleY(scaleFactor).
setDuration(duration).
setInterpolator(new Workspace.ZoomInInterpolator());
final ObjectAnimator alphaAnim = LauncherAnimUtils
.ofFloat(fromView, "alpha", 1f, 0f)
.setDuration(fadeOutDuration);
alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = 1f - (Float) animation.getAnimatedValue();
dispatchOnLauncherTransitionStep(fromView, t);
dispatchOnLauncherTransitionStep(toView, t);
}
});
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionPrepare(toView, animated, true);
mAppsCustomizeContent.pauseScrolling();
mStateAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateWallpaperVisibility(true);
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionEnd(fromView, animated, true);
dispatchOnLauncherTransitionEnd(toView, animated, true);
if (mWorkspace != null) {
mWorkspace.hideScrollingIndicator(false);
}
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
mAppsCustomizeContent.updateCurrentPageScroll();
mAppsCustomizeContent.resumeScrolling();
}
});
mStateAnimation.playTogether(scaleAnim, alphaAnim);
if (workspaceAnim != null) {
mStateAnimation.play(workspaceAnim);
}
dispatchOnLauncherTransitionStart(fromView, animated, true);
dispatchOnLauncherTransitionStart(toView, animated, true);
LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
} else {
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionStart(fromView, animated, true);
dispatchOnLauncherTransitionEnd(fromView, animated, true);
dispatchOnLauncherTransitionPrepare(toView, animated, true);
dispatchOnLauncherTransitionStart(toView, animated, true);
dispatchOnLauncherTransitionEnd(toView, animated, true);
mWorkspace.hideScrollingIndicator(false);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
mAppsCustomizeTabHost.onTrimMemory();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (!hasFocus) {
// When another window occludes launcher (like the notification shade, or recents),
// ensure that we enable the wallpaper flag so that transitions are done correctly.
updateWallpaperVisibility(true);
} else {
// When launcher has focus again, disable the wallpaper if we are in AllApps
mWorkspace.postDelayed(new Runnable() {
@Override
public void run() {
disableWallpaperIfInAllApps();
}
}, 500);
}
}
void showWorkspace(boolean animated) {
showWorkspace(animated, null);
}
void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
if (mState != State.WORKSPACE) {
boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
mWorkspace.setVisibility(View.VISIBLE);
hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
// Show the search bar (only animate if we were showing the drop target bar in spring
// loaded mode)
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.showSearchBar(wasInSpringLoadedMode);
}
// We only need to animate in the dock divider if we're going from spring loaded mode
showDockDivider(animated && wasInSpringLoadedMode);
// 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_WINDOW_STATE_CHANGED);
}
void showAllApps(boolean animated) {
if (mState != State.WORKSPACE) return;
showAppsCustomizeHelper(animated, false);
mAppsCustomizeTabHost.requestFocus();
// 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_WINDOW_STATE_CHANGED);
}
void enterSpringLoadedDragMode() {
if (isAllAppsVisible()) {
hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null);
hideDockDivider();
mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
}
}
void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
final Runnable onCompleteRunnable) {
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);
showWorkspace(true, onCompleteRunnable);
} 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) {
mQsbDivider.setVisibility(View.INVISIBLE);
mDockDivider.setVisibility(View.INVISIBLE);
}
}
void showDockDivider(boolean animated) {
if (mQsbDivider != null && mDockDivider != null) {
mQsbDivider.setVisibility(View.VISIBLE);
mDockDivider.setVisibility(View.VISIBLE);
if (mDividerAnimator != null) {
mDividerAnimator.cancel();
mQsbDivider.setAlpha(1f);
mDockDivider.setAlpha(1f);
mDividerAnimator = null;
}
if (animated) {
mDividerAnimator = LauncherAnimUtils.createAnimatorSet();
mDividerAnimator.playTogether(LauncherAnimUtils.ofFloat(mQsbDivider, "alpha", 1f),
LauncherAnimUtils.ofFloat(mDockDivider, "alpha", 1f));
int duration = 0;
if (mSearchDropTargetBar != null) {
duration = mSearchDropTargetBar.getTransitionInDuration();
}
mDividerAnimator.setDuration(duration);
mDividerAnimator.start();
}
}
}
void lockAllApps() {
// TODO
}
void unlockAllApps() {
// TODO
}
/**
* Shows the hotseat area.
*/
void showHotseat(boolean animated) {
if (!LauncherApplication.isScreenLarge()) {
if (animated) {
if (mHotseat.getAlpha() != 1f) {
int duration = 0;
if (mSearchDropTargetBar != null) {
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) {
if (mHotseat.getAlpha() != 0f) {
int duration = 0;
if (mSearchDropTargetBar != null) {
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(isHotseatLayout(layout));
}
}
/** Maps the current orientation to an index for referencing orientation correct global icons */
private int getCurrentOrientationIndexForGlobalIcons() {
// default - 0, landscape - 1
switch (getResources().getConfiguration().orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
return 1;
default:
return 0;
}
}
private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
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(resourceName);
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,
String toolbarResourceName) {
Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
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,
String toolbarResourceName) {
ImageView button = (ImageView) findViewById(buttonId);
Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
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 d) {
TextView button = (TextView) findViewById(buttonId);
button.setCompoundDrawables(d, 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 voiceButtonContainer = findViewById(R.id.voice_button_container);
final View voiceButton = findViewById(R.id.voice_button);
final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
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,
TOOLBAR_SEARCH_ICON_METADATA_NAME);
if (sGlobalSearchIcon[coi] == null) {
sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
TOOLBAR_ICON_METADATA_NAME);
}
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 (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
searchButton.setVisibility(View.GONE);
voiceButton.setVisibility(View.GONE);
if (voiceButtonProxy != null) {
voiceButtonProxy.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 voiceButtonContainer = findViewById(R.id.voice_button_container);
final View voiceButton = findViewById(R.id.voice_button);
final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
// We only show/update the voice search icon if the search icon is enabled as well
final SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
ComponentName activityName = null;
if (globalSearchActivity != null) {
// Check if the global search activity handles voice search
Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
intent.setPackage(globalSearchActivity.getPackageName());
activityName = intent.resolveActivity(getPackageManager());
}
if (activityName == null) {
// Fallback: check if an activity other than the global search activity
// resolves this
Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
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,
TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
if (sVoiceSearchIcon[coi] == null) {
sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
TOOLBAR_ICON_METADATA_NAME);
}
if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
voiceButton.setVisibility(View.VISIBLE);
if (voiceButtonProxy != null) {
voiceButtonProxy.setVisibility(View.VISIBLE);
}
invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
return true;
} else {
if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
voiceButton.setVisibility(View.GONE);
if (voiceButtonProxy != null) {
voiceButtonProxy.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,
TOOLBAR_ICON_METADATA_NAME);
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) {
// Ensure that the new drawable we are creating has the approprate toolbar icon bounds
Resources r = getResources();
Drawable marketIconDrawable = d.newDrawable(r);
int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
marketIconDrawable.setBounds(0, 0, w, h);
updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
final boolean result = super.dispatchPopulateAccessibilityEvent(event);
final List<CharSequence> text = event.getText();
text.clear();
// Populate event with a fake title based on the current state.
if (mState == State.APPS_CUSTOMIZE) {
text.add(getString(R.string.all_apps_button_label));
} else {
text.add(getString(R.string.all_apps_home_button_label));
}
return result;
}
/**
* 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 run the passed Runnable
* 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.
*/
private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
if (mPaused) {
Log.i(TAG, "Deferring update until onResume");
if (deletePreviousRunnables) {
while (mOnResumeCallbacks.remove(run)) {
}
}
mOnResumeCallbacks.add(run);
return true;
} else {
return false;
}
}
private boolean waitUntilResume(Runnable run) {
return waitUntilResume(run, false);
}
/**
* 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() {
// If we're starting binding all over again, clear any bind calls we'd postponed in
// the past (see waitUntilResume) -- we don't need them since we're starting binding
// from scratch again
mOnResumeCallbacks.clear();
final Workspace workspace = mWorkspace;
mNewShortcutAnimatePage = -1;
mNewShortcutAnimateViews.clear();
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(final ArrayList<ItemInfo> shortcuts, final int start, final int end) {
if (waitUntilResume(new Runnable() {
public void run() {
bindItems(shortcuts, start, end);
}
})) {
return;
}
// Get the list of added shortcuts and intersect them with the set of shortcuts here
Set<String> newApps = new HashSet<String>();
newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
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:
ShortcutInfo info = (ShortcutInfo) item;
String uri = info.intent.toUri(0).toString();
View shortcut = createShortcut(info);
workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);
boolean animateIconUp = false;
synchronized (newApps) {
if (newApps.contains(uri)) {
animateIconUp = newApps.remove(uri);
}
}
if (animateIconUp) {
// Prepare the view to be animated up
shortcut.setAlpha(0f);
shortcut.setScaleX(0f);
shortcut.setScaleY(0f);
mNewShortcutAnimatePage = item.screen;
if (!mNewShortcutAnimateViews.contains(shortcut)) {
mNewShortcutAnimateViews.add(shortcut);
}
}
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(final HashMap<Long, FolderInfo> folders) {
if (waitUntilResume(new Runnable() {
public void run() {
bindFolders(folders);
}
})) {
return;
}
sFolders.clear();
sFolders.putAll(folders);
}
/**
* Add the views for a widget to the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppWidget(final LauncherAppWidgetInfo item) {
if (waitUntilResume(new Runnable() {
public void run() {
bindAppWidget(item);
}
})) {
return;
}
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.setTag(item);
item.onBindAppWidget(this);
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");
}
}
public void onPageBoundSynchronously(int page) {
mSynchronouslyBoundPages.add(page);
}
/**
* Callback saying that there aren't any more items to bind.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems() {
if (waitUntilResume(new Runnable() {
public void run() {
finishBindingItems();
}
})) {
return;
}
if (mSavedState != null) {
if (!mWorkspace.hasFocus()) {
mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
}
mSavedState = null;
}
mWorkspace.restoreInstanceStateForRemainingPages();
// 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();
// Animate up any icons as necessary
if (mVisible || mWorkspaceLoading) {
Runnable newAppsRunnable = new Runnable() {
@Override
public void run() {
runNewAppsAnimation(false);
}
};
boolean willSnapPage = mNewShortcutAnimatePage > -1 &&
mNewShortcutAnimatePage != mWorkspace.getCurrentPage();
if (canRunNewAppsAnimation()) {
// If the user has not interacted recently, then either snap to the new page to show
// the new-apps animation or just run them if they are to appear on the current page
if (willSnapPage) {
mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
} else {
runNewAppsAnimation(false);
}
} else {
// If the user has interacted recently, then just add the items in place if they
// are on another page (or just normally if they are added to the current page)
runNewAppsAnimation(willSnapPage);
}
}
mWorkspaceLoading = false;
}
private boolean canRunNewAppsAnimation() {
long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
}
/**
* Runs a new animation that scales up icons that were added while Launcher was in the
* background.
*
* @param immediate whether to run the animation or show the results immediately
*/
private void runNewAppsAnimation(boolean immediate) {
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
Collection<Animator> bounceAnims = new ArrayList<Animator>();
// Order these new views spatially so that they animate in order
Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
@Override
public int compare(View a, View b) {
CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams();
CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams();
int cellCountX = LauncherModel.getCellCountX();
return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
}
});
// Animate each of the views in place (or show them immediately if requested)
if (immediate) {
for (View v : mNewShortcutAnimateViews) {
v.setAlpha(1f);
v.setScaleX(1f);
v.setScaleY(1f);
}
} else {
for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
View v = mNewShortcutAnimateViews.get(i);
ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
PropertyValuesHolder.ofFloat("alpha", 1f),
PropertyValuesHolder.ofFloat("scaleX", 1f),
PropertyValuesHolder.ofFloat("scaleY", 1f));
bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
bounceAnims.add(bounceAnim);
}
anim.playTogether(bounceAnims);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mWorkspace != null) {
mWorkspace.postDelayed(mBuildLayersRunnable, 500);
}
}
});
anim.start();
}
// Clean up
mNewShortcutAnimatePage = -1;
mNewShortcutAnimateViews.clear();
new Thread("clearNewAppsThread") {
public void run() {
mSharedPrefs.edit()
.putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1)
.putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null)
.commit();
}
}.start();
}
@Override
public void bindSearchablesChanged() {
boolean searchVisible = updateGlobalSearchIcon();
boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
}
}
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
Runnable setAllAppsRunnable = new Runnable() {
public void run() {
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(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(setAllAppsRunnable);
} else {
// If we did not initialize the spinner in onCreate, then we can directly set the
// list of applications without waiting for any progress bars views to be hidden.
setAllAppsRunnable.run();
}
}
/**
* A package was installed.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppsAdded(final ArrayList<ApplicationInfo> apps) {
if (waitUntilResume(new Runnable() {
public void run() {
bindAppsAdded(apps);
}
})) {
return;
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.addApps(apps);
}
}
/**
* A package was updated.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppsUpdated(final ArrayList<ApplicationInfo> apps) {
if (waitUntilResume(new Runnable() {
public void run() {
bindAppsUpdated(apps);
}
})) {
return;
}
if (mWorkspace != null) {
mWorkspace.updateShortcuts(apps);
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.updateApps(apps);
}
}
/**
* A package was uninstalled. We take both the super set of packageNames
* in addition to specific applications to remove, the reason being that
* this can be called when a package is updated as well. In that scenario,
* we only remove specific components from the workspace, where as
* package-removal should clear all items by package name.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindComponentsRemoved(final ArrayList<String> packageNames,
final ArrayList<ApplicationInfo> appInfos,
final boolean matchPackageNamesOnly, final UserHandle user) {
if (waitUntilResume(new Runnable() {
public void run() {
bindComponentsRemoved(packageNames, appInfos, matchPackageNamesOnly, user);
}
})) {
return;
}
if (matchPackageNamesOnly) {
mWorkspace.removeItemsByPackageName(packageNames, user);
} else {
mWorkspace.removeItemsByApplicationInfo(appInfos, user);
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.removeApps(appInfos);
}
// Notify the drag controller
mDragController.onAppsRemoved(appInfos, this);
}
/**
* A number of packages were updated.
*/
private ArrayList<Object> mWidgetsAndShortcuts;
private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
public void run() {
bindPackagesUpdated(mWidgetsAndShortcuts);
mWidgetsAndShortcuts = null;
}
};
public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
mWidgetsAndShortcuts = widgetsAndShortcuts;
return;
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
}
}
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 boolean isRotationEnabled() {
boolean enableRotation = sForceEnableRotation ||
getResources().getBoolean(R.bool.allow_rotation);
return enableRotation;
}
public void lockScreenOrientation() {
if (isRotationEnabled()) {
setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
.getConfiguration().orientation));
}
}
public void unlockScreenOrientation(boolean immediate) {
if (isRotationEnabled()) {
if (immediate) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
} else {
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;
if (mHostDevice == HostDevice.UNKNOWN) {
mHostDevice = "1".equals(getSystemPropertyFromShell("ro.kernel.qemu")) ?
HostDevice.EMULATOR : HostDevice.REAL_DEVICE;
}
// disable clings when running inside emulator
if (mHostDevice == HostDevice.EMULATOR) return false;
// Restricted secondary users (child mode) will potentially have very few apps
// seeded when they start up for the first time. Clings won't work well with that
boolean supportsLimitedUsers =
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
Account[] accounts = AccountManager.get(this).getAccounts();
if (supportsLimitedUsers && accounts.length == 0) {
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
Bundle restrictions = um.getUserRestrictions();
if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
return false;
}
}
// Check if the system has requested skipping of first-use hints.
if (Settings.Secure.getInt(getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, 0) == 1) {
return false;
}
return true;
}
private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) {
final 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);
}
cling.setFocusableInTouchMode(true);
cling.post(new Runnable() {
public void run() {
cling.setFocusable(true);
cling.requestFocus();
}
});
mHideFromAccessibilityHelper.setImportantForAccessibilityToNo(
mDragLayer, clingId == R.id.all_apps_cling);
}
return cling;
}
private void dismissCling(final Cling cling, final String flag, int duration) {
// To catch cases where siblings of top-level views are made invisible, just check whether
// the cling is directly set to GONE before dismissing it.
if (cling != null && cling.getVisibility() != View.GONE) {
ObjectAnimator anim = LauncherAnimUtils.ofFloat(cling, "alpha", 0f);
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
cling.setVisibility(View.GONE);
cling.cleanup();
// We should update the shared preferences on a background thread
new Thread("dismissClingThread") {
public void run() {
SharedPreferences.Editor editor = mSharedPrefs.edit();
editor.putBoolean(flag, true);
editor.commit();
}
}.start();
};
});
anim.start();
mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
}
}
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);
}
});
mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
}
}
private boolean skipCustomClingIfNoAccounts() {
Cling cling = (Cling) findViewById(R.id.workspace_cling);
boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
if (customCling) {
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccountsByType("com.google");
return accounts.length == 0;
}
return false;
}
public void showFirstRunWorkspaceCling() {
// Enable the clings only if they have not been dismissed before
if (isClingsEnabled() &&
!mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false) &&
!skipCustomClingIfNoAccounts() ) {
// If we're not using the default workspace layout, replace workspace cling
// with a custom workspace cling (usually specified in an overlay)
// For now, only do this on tablets
if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
getResources().getBoolean(R.bool.config_useCustomClings)) {
// Use a custom cling
View cling = findViewById(R.id.workspace_cling);
ViewGroup clingParent = (ViewGroup) cling.getParent();
int clingIndex = clingParent.indexOfChild(cling);
clingParent.removeViewAt(clingIndex);
View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
clingParent.addView(customCling, clingIndex);
customCling.setId(R.id.workspace_cling);
}
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
if (isClingsEnabled() &&
!mSharedPrefs.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
if (isClingsEnabled() &&
!mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
return initCling(R.id.folder_cling, null, true, 0);
} else {
removeCling(R.id.folder_cling);
return null;
}
}
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);
}
/**
* 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));
}
}
public static void dumpDebugLogsToConsole() {
Log.d(TAG, "");
Log.d(TAG, "*********************");
Log.d(TAG, "Launcher debug logs: ");
for (int i = 0; i < sDumpLogs.size(); i++) {
Log.d(TAG, " " + sDumpLogs.get(i));
}
Log.d(TAG, "*********************");
Log.d(TAG, "");
}
}
interface LauncherTransitionable {
View getContent();
void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
void onLauncherTransitionStep(Launcher l, float t);
void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
}