Files
Lawnchair/src/com/android/launcher2/Launcher.java
T
Bjorn Bringert 98047dc39f Clear search widget animation in Launcher.onResume()
Before, Launcher relied on a callback to a SearchManager.OnCancelListener to clear the search widget
animation. However, that doesn't get called if some other app opens the search dialog
before the Launcher is reopened. This change clears the animation in onResume().

Should fix http://b/issue?id=2246760
2009-11-21 22:08:04 +00:00

2223 lines
80 KiB
Java

/*
* 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.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.LiveFolders;
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.View;
import android.view.ViewGroup;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.LinearLayout;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.DataInputStream;
/**
* Default launcher application.
*/
public final class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks {
static final String TAG = "Launcher";
static final boolean LOGD = false;
static final boolean PROFILE_STARTUP = false;
static final boolean PROFILE_ROTATE = false;
static final boolean DEBUG_USER_INTERFACE = false;
private static final int WALLPAPER_SCREENS_SPAN = 2;
private static final int MENU_GROUP_ADD = 1;
private static final int MENU_ADD = Menu.FIRST + 1;
private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
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_LIVE_FOLDER = 8;
private static final int REQUEST_PICK_APPWIDGET = 9;
private static final int REQUEST_PICK_WALLPAPER = 10;
static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
static final String EXTRA_CUSTOM_WIDGET = "custom_widget";
static final String SEARCH_WIDGET = "search_widget";
static final int SCREEN_COUNT = 5;
static final int DEFAULT_SCREEN = 2;
static final int NUMBER_CELLS_X = 4;
static final int NUMBER_CELLS_Y = 4;
static final int DIALOG_CREATE_SHORTCUT = 1;
static final int DIALOG_RENAME_FOLDER = 2;
private static final String PREFERENCES = "launcher.preferences";
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
// Type: boolean
private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder";
// Type: long
private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
// 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_cellX";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY";
// Type: int[]
private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells";
// 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";
static final int APPWIDGET_HOST_ID = 1024;
private static final Object sLock = new Object();
private static int sScreen = DEFAULT_SCREEN;
private final BroadcastReceiver mCloseSystemDialogsReceiver
= new CloseSystemDialogsIntentReceiver();
private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
private LayoutInflater mInflater;
private DragController mDragController;
private Workspace mWorkspace;
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private CellLayout.CellInfo mAddItemCellInfo;
private CellLayout.CellInfo mMenuAddInfo;
private final int[] mCellCoordinates = new int[2];
private FolderInfo mFolderInfo;
private DeleteZone mDeleteZone;
private HandleView mHandleView;
private AllAppsView mAllAppsGrid;
private Bundle mSavedState;
private SpannableStringBuilder mDefaultKeySsb = null;
private boolean mIsNewIntent;
private boolean mWorkspaceLoading = true;
private boolean mRestoring;
private boolean mWaitingForResult;
private Bundle mSavedInstanceState;
private LauncherModel mModel;
private ArrayList<ItemInfo> mDesktopItems = new ArrayList<ItemInfo>();
private static HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
private ImageView mPreviousView;
private ImageView mNextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mModel = ((LauncherApplication)getApplication()).setLauncher(this);
mDragController = new DragController(this);
mInflater = getLayoutInflater();
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(mCloseSystemDialogsReceiver, filter);
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
if (PROFILE_STARTUP) {
android.os.Debug.startMethodTracing("/sdcard/launcher");
}
checkForLocaleChange();
setWallpaperDimension();
setContentView(R.layout.launcher);
setupViews();
registerContentObservers();
lockAllApps();
mSavedState = savedInstanceState;
restoreState(mSavedState);
if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}
// We have a new AllAppsView, we need to re-bind everything, and it could have
// changed in our absence.
mModel.setAllAppsDirty();
mModel.setWorkspaceDirty();
if (!mRestoring) {
mModel.startLoader(this, true);
}
// For handling default keys
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
}
private void checkForLocaleChange() {
final LocaleConfiguration localeConfiguration = new LocaleConfiguration();
readConfiguration(this, localeConfiguration);
final Configuration configuration = getResources().getConfiguration();
final String previousLocale = localeConfiguration.locale;
final String locale = configuration.locale.toString();
final int previousMcc = localeConfiguration.mcc;
final int mcc = configuration.mcc;
final int previousMnc = localeConfiguration.mnc;
final int mnc = configuration.mnc;
boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
if (localeChanged) {
localeConfiguration.locale = locale;
localeConfiguration.mcc = mcc;
localeConfiguration.mnc = mnc;
writeConfiguration(this, localeConfiguration);
AppInfoCache.flush();
}
}
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
}
}
}
}
static int getScreen() {
synchronized (sLock) {
return sScreen;
}
}
static void setScreen(int screen) {
synchronized (sLock) {
sScreen = screen;
}
}
private void setWallpaperDimension() {
WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE);
Display display = getWindowManager().getDefaultDisplay();
boolean isPortrait = display.getWidth() < display.getHeight();
final int width = isPortrait ? display.getWidth() : display.getHeight();
final int height = isPortrait ? display.getHeight() : display.getWidth();
wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mWaitingForResult = false;
// The pattern used here is that a user PICKs a specific application,
// which, depending on the target, might need to CREATE the actual target.
// For example, the user would PICK_SHORTCUT for "Music playlist", and we
// launch over to the Music app to actually CREATE_SHORTCUT.
if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
switch (requestCode) {
case REQUEST_PICK_APPLICATION:
completeAddApplication(this, data, mAddItemCellInfo);
break;
case REQUEST_PICK_SHORTCUT:
processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT);
break;
case REQUEST_CREATE_SHORTCUT:
completeAddShortcut(data, mAddItemCellInfo);
break;
case REQUEST_PICK_LIVE_FOLDER:
addLiveFolder(data);
break;
case REQUEST_CREATE_LIVE_FOLDER:
completeAddLiveFolder(data, mAddItemCellInfo);
break;
case REQUEST_PICK_APPWIDGET:
addAppWidget(data);
break;
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(data, mAddItemCellInfo);
break;
case REQUEST_PICK_WALLPAPER:
// We just wanted the activity result here so we can clear mWaitingForResult
break;
}
} else if ((requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED &&
data != null) {
// Clean up the appWidgetId if we canceled
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (appWidgetId != -1) {
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
}
}
}
@Override
protected void onResume() {
super.onResume();
if (mRestoring) {
mWorkspaceLoading = true;
mModel.startLoader(this, true);
mRestoring = false;
}
// If this was a new intent (i.e., the mIsNewIntent flag got set to true by
// onNewIntent), then close the search dialog if needed, because it probably
// came from the user pressing 'home' (rather than, for example, pressing 'back').
if (mIsNewIntent) {
// Post to a handler so that this happens after the search dialog tries to open
// itself again.
mWorkspace.post(new Runnable() {
public void run() {
stopSearch();
}
});
}
mIsNewIntent = false;
}
@Override
protected void onPause() {
super.onPause();
dismissPreview(mPreviousView);
dismissPreview(mNextView);
mDragController.cancelDrag();
}
@Override
public Object onRetainNonConfigurationInstance() {
// Flag the loader to stop early before switching
mModel.stopLoader();
if (PROFILE_ROTATE) {
android.os.Debug.startMethodTracing("/sdcard/launcher-rotate");
}
return null;
}
private boolean acceptFilter() {
final InputMethodManager inputManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
return !inputManager.isFullscreenMode();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean handled = super.onKeyDown(keyCode, event);
if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) {
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();
}
}
return handled;
}
private String getTypedText() {
return mDefaultKeySsb.toString();
}
private void clearTypedText() {
mDefaultKeySsb.clear();
mDefaultKeySsb.clearSpans();
Selection.setSelection(mDefaultKeySsb, 0);
}
/**
* Restores the previous state, if it exists.
*
* @param savedState The previous state.
*/
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
}
final boolean allApps = savedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false);
if (allApps) {
showAllApps(false);
}
final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
if (currentScreen > -1) {
mWorkspace.setCurrentScreen(currentScreen);
}
final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
if (addScreen > -1) {
mAddItemCellInfo = new CellLayout.CellInfo();
final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
addItemCellInfo.valid = true;
addItemCellInfo.screen = addScreen;
addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
addItemCellInfo.findVacantCellsFromOccupied(
savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS),
savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X),
savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y));
mRestoring = true;
}
boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
if (renameFolder) {
long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
mFolderInfo = mModel.getFolderById(this, mFolders, id);
mRestoring = true;
}
}
/**
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
DragController dragController = mDragController;
DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
dragLayer.setDragController(dragController);
mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view);
mAllAppsGrid.setLauncher(this);
mAllAppsGrid.setDragController(dragController);
mAllAppsGrid.setWillNotDraw(false); // We don't want a hole punched in our window.
// Manage focusability manually since this thing is always visible
mAllAppsGrid.setFocusable(false);
mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
final Workspace workspace = mWorkspace;
DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
mDeleteZone = deleteZone;
mHandleView = (HandleView) findViewById(R.id.all_apps_button);
mHandleView.setLauncher(this);
mHandleView.setOnClickListener(this);
mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
Drawable previous = mPreviousView.getDrawable();
Drawable next = mNextView.getDrawable();
mWorkspace.setIndicators(previous, next);
mPreviousView.setHapticFeedbackEnabled(false);
mPreviousView.setOnLongClickListener(this);
mNextView.setHapticFeedbackEnabled(false);
mNextView.setOnLongClickListener(this);
workspace.setOnLongClickListener(this);
workspace.setDragController(dragController);
workspace.setLauncher(this);
deleteZone.setLauncher(this);
deleteZone.setDragController(dragController);
deleteZone.setHandle(mHandleView);
dragController.setDragScoller(workspace);
dragController.setDragListener(deleteZone);
dragController.setScrollView(dragLayer);
// The order here is bottom to top.
dragController.addDropTarget(workspace);
dragController.addDropTarget(deleteZone);
}
@SuppressWarnings({"UnusedDeclaration"})
public void previousScreen(View v) {
if (!isAllAppsVisible()) {
mWorkspace.scrollLeft();
}
}
@SuppressWarnings({"UnusedDeclaration"})
public void nextScreen(View v) {
if (!isAllAppsVisible()) {
mWorkspace.scrollRight();
}
}
/**
* Creates a view representing a shortcut.
*
* @param info The data structure describing the shortcut.
*
* @return A View inflated from R.layout.application.
*/
View createShortcut(ApplicationInfo info) {
return createShortcut(R.layout.application,
(ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), 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, ApplicationInfo info) {
TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
if (info.icon == null) {
info.icon = AppInfoCache.getIconDrawable(getPackageManager(), info);
}
if (!info.filtered) {
info.icon = Utilities.createIconThumbnail(info.icon, this);
info.filtered = true;
}
favorite.setCompoundDrawablesWithIntrinsicBounds(null, info.icon, null, null);
favorite.setText(info.title);
favorite.setTag(info);
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(Context context, Intent data, CellLayout.CellInfo cellInfo) {
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
final ApplicationInfo info = infoFromApplicationIntent(context, data);
if (info != null) {
mWorkspace.addApplicationShortcut(info, cellInfo, isWorkspaceLocked());
}
}
private static ApplicationInfo infoFromApplicationIntent(Context context, Intent data) {
ComponentName component = data.getComponent();
PackageManager packageManager = context.getPackageManager();
ActivityInfo activityInfo = null;
try {
activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */);
} catch (NameNotFoundException e) {
Log.e(TAG, "Couldn't find ActivityInfo for selected application", e);
}
if (activityInfo != null) {
ApplicationInfo itemInfo = new ApplicationInfo();
itemInfo.title = activityInfo.loadLabel(packageManager);
if (itemInfo.title == null) {
itemInfo.title = activityInfo.name;
}
itemInfo.setActivity(component, Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
itemInfo.icon = activityInfo.loadIcon(packageManager);
itemInfo.container = ItemInfo.NO_ID;
return itemInfo;
}
return null;
}
/**
* 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, CellLayout.CellInfo cellInfo) {
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
final ApplicationInfo info = addShortcut(this, data, cellInfo, false);
if (!mRestoring) {
final View view = createShortcut(info);
mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
isWorkspaceLocked());
}
}
/**
* Add a widget to the workspace.
*
* @param data The intent describing the appWidgetId.
* @param cellInfo The position on screen where to create the widget.
*/
private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
Bundle extras = data.getExtras();
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
// Calculate the grid spans needed to fit this widget
CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
// Try finding open space on Launcher screen
final int[] xy = mCellCoordinates;
if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
return;
}
// Build Launcher-specific widget info and save to database
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
launcherInfo.spanX = spans[0];
launcherInfo.spanY = spans[1];
LauncherModel.addItemToDatabase(this, launcherInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
if (!mRestoring) {
mDesktopItems.add(launcherInfo);
// Perform actual inflation because we're live
launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
launcherInfo.hostView.setTag(launcherInfo);
mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
}
}
public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
mDesktopItems.remove(launcherInfo);
launcherInfo.hostView = null;
}
public LauncherAppWidgetHost getAppWidgetHost() {
return mAppWidgetHost;
}
static ApplicationInfo addShortcut(Context context, Intent data,
CellLayout.CellInfo cellInfo, boolean notify) {
final ApplicationInfo info = infoFromShortcutIntent(context, data);
LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
return info;
}
private static ApplicationInfo infoFromShortcutIntent(Context context, Intent data) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
Drawable icon = null;
boolean filtered = false;
boolean customIcon = false;
ShortcutIconResource iconResource = null;
if (bitmap != null) {
icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
filtered = true;
customIcon = true;
} else {
Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
if (extra != null && extra instanceof ShortcutIconResource) {
try {
iconResource = (ShortcutIconResource) extra;
final PackageManager packageManager = context.getPackageManager();
Resources resources = packageManager.getResourcesForApplication(
iconResource.packageName);
final int id = resources.getIdentifier(iconResource.resourceName, null, null);
icon = resources.getDrawable(id);
} catch (Exception e) {
Log.w(TAG, "Could not load shortcut icon: " + extra);
}
}
}
if (icon == null) {
icon = context.getPackageManager().getDefaultActivityIcon();
}
final ApplicationInfo info = new ApplicationInfo();
info.icon = icon;
info.filtered = filtered;
info.title = name;
info.intent = intent;
info.customIcon = customIcon;
info.iconResource = iconResource;
return info;
}
void closeSystemDialogs() {
closeAllApps(true);
getWindow().closeAllPanels();
try {
dismissDialog(DIALOG_CREATE_SHORTCUT);
// Unlock the workspace if the dialog was showing
} catch (Exception e) {
// An exception is thrown if the dialog is not visible, which is fine
}
try {
dismissDialog(DIALOG_RENAME_FOLDER);
// Unlock the workspace if the dialog was showing
} catch (Exception e) {
// An exception is thrown if the dialog is not visible, which is fine
}
// Whatever we were doing is hereby canceled.
mWaitingForResult = false;
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// Close the menu
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
// also will cancel mWaitingForResult.
closeSystemDialogs();
// Set this flag so that onResume knows to close the search dialog if it's open,
// because this was a new intent (thus a press of 'home' or some such) rather than
// for example onResume being called when the user pressed the 'back' button.
mIsNewIntent = true;
boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
boolean allAppsVisible = isAllAppsVisible();
if (!mWorkspace.isDefaultScreenShowing()) {
mWorkspace.moveToDefaultScreen(alreadyOnHome && !allAppsVisible);
}
closeAllApps(alreadyOnHome && allAppsVisible);
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(
INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Do not call super here
mSavedInstanceState = savedInstanceState;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen());
final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
if (folders.size() > 0) {
final int count = folders.size();
long[] ids = new long[count];
for (int i = 0; i < count; i++) {
final FolderInfo info = folders.get(i).getInfo();
ids[i] = info.id;
}
outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids);
} else {
super.onSaveInstanceState(outState);
}
// TODO should not do this if the drawer is currently closing.
if (isAllAppsVisible()) {
outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
}
if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen);
outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX);
outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY);
outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX());
outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY());
outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS,
layout.getOccupiedCells());
}
if (mFolderInfo != null && mWaitingForResult) {
outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
}
}
@Override
public void onDestroy() {
super.onDestroy();
try {
mAppWidgetHost.stopListening();
} catch (NullPointerException ex) {
Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
}
TextKeyListener.getInstance().release();
mModel.stopLoader();
unbindDesktopItems();
AppInfoCache.unbindDrawables();
getContentResolver().unregisterContentObserver(mWidgetObserver);
dismissPreview(mPreviousView);
dismissPreview(mNextView);
unregisterReceiver(mCloseSystemDialogsReceiver);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
if (requestCode >= 0) mWaitingForResult = true;
super.startActivityForResult(intent, requestCode);
}
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
closeAllApps(true);
// Slide the search widget to the top, if it's on the current screen,
// otherwise show the search dialog immediately.
Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
if (searchWidget == null) {
showSearchDialog(initialQuery, selectInitialQuery, appSearchData, globalSearch);
} else {
searchWidget.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
// show the currently typed text in the search widget while sliding
searchWidget.setQuery(getTypedText());
}
}
/**
* Show the search dialog immediately, without changing the search widget.
*
* @see Activity#startSearch(String, boolean, android.os.Bundle, boolean)
*/
void showSearchDialog(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
if (initialQuery == null) {
// Use any text typed in the launcher as the initial query
initialQuery = getTypedText();
clearTypedText();
}
if (appSearchData == null) {
appSearchData = new Bundle();
appSearchData.putString(SearchManager.SOURCE, "launcher-search");
}
final SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
final Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
if (searchWidget != null) {
// This gets called when the user leaves the search dialog to go back to
// the Launcher.
searchManager.setOnCancelListener(new SearchManager.OnCancelListener() {
public void onCancel() {
searchManager.setOnCancelListener(null);
stopSearch();
}
});
}
searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
}
/**
* Cancel search dialog if it is open.
*/
void stopSearch() {
// Close search dialog
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchManager.stopSearch();
// Restore search widget to its normal position
Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
if (searchWidget != null) {
searchWidget.stopSearch(false);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (isWorkspaceLocked()) {
return false;
}
super.onCreateOptionsMenu(menu);
menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
.setIcon(android.R.drawable.ic_menu_add)
.setAlphabeticShortcut('A');
menu.add(0, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
.setIcon(android.R.drawable.ic_menu_gallery)
.setAlphabeticShortcut('W');
menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
.setIcon(android.R.drawable.ic_search_category_default)
.setAlphabeticShortcut(SearchManager.MENU_KEY);
menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications)
.setIcon(com.android.internal.R.drawable.ic_menu_notifications)
.setAlphabeticShortcut('N');
final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
.setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
.setIntent(settings);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
mMenuAddInfo = mWorkspace.findAllVacantCells(null);
menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ADD:
addItems();
return true;
case MENU_WALLPAPER_SETTINGS:
startWallpaper();
return true;
case MENU_SEARCH:
onSearchRequested();
return true;
case MENU_NOTIFICATIONS:
showNotifications();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Indicates that we want global search for this activity by setting the globalSearch
* argument for {@link #startSearch} to true.
*/
@Override
public boolean onSearchRequested() {
startSearch(null, false, null, true);
return true;
}
public boolean isWorkspaceLocked() {
return mWorkspaceLoading || mWaitingForResult;
}
private void addItems() {
closeAllApps(true);
showAddDialog(mMenuAddInfo);
}
void addAppWidget(Intent data) {
// TODO: catch bad widget exception when sent
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
String customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET);
if (SEARCH_WIDGET.equals(customWidget)) {
// We don't need this any more, since this isn't a real app widget.
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
// add the search widget
addSearch();
} else {
AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (appWidget.configure != null) {
// Launch over to configure widget, if needed
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.setComponent(appWidget.configure);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
} else {
// Otherwise just add it
onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
}
}
}
void addSearch() {
final Widget info = Widget.makeSearch();
final CellLayout.CellInfo cellInfo = mAddItemCellInfo;
final int[] xy = mCellCoordinates;
final int spanX = info.spanX;
final int spanY = info.spanY;
if (!findSlot(cellInfo, xy, spanX, spanY)) return;
LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
final View view = mInflater.inflate(info.layoutResource, null);
view.setTag(info);
Search search = (Search) view.findViewById(R.id.widget_search);
search.setLauncher(this);
mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
}
void processShortcut(Intent intent, int requestCodeApplication, int requestCodeShortcut) {
// 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);
startActivityForResult(pickIntent, requestCodeApplication);
} else {
startActivityForResult(intent, requestCodeShortcut);
}
}
void addLiveFolder(Intent intent) {
// Handle case where user selected "Folder"
String folderName = getResources().getString(R.string.group_folder);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
if (folderName != null && folderName.equals(shortcutName)) {
addFolder();
} else {
startActivityForResult(intent, REQUEST_CREATE_LIVE_FOLDER);
}
}
void addFolder() {
UserFolderInfo folderInfo = new UserFolderInfo();
folderInfo.title = getText(R.string.folder_name);
CellLayout.CellInfo cellInfo = mAddItemCellInfo;
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
// Update the model
LauncherModel.addItemToDatabase(this, folderInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false);
mFolders.put(folderInfo.id, folderInfo);
// Create the view
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo);
mWorkspace.addInCurrentScreen(newFolder,
cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked());
}
void removeFolder(FolderInfo folder) {
mFolders.remove(folder.id);
}
private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) {
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false);
if (!mRestoring) {
final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this,
(ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
isWorkspaceLocked());
}
}
static LiveFolderInfo addLiveFolder(Context context, Intent data,
CellLayout.CellInfo cellInfo, boolean notify) {
Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT);
String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME);
Drawable icon = null;
boolean filtered = false;
Intent.ShortcutIconResource iconResource = null;
Parcelable extra = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON);
if (extra != null && extra instanceof Intent.ShortcutIconResource) {
try {
iconResource = (Intent.ShortcutIconResource) extra;
final PackageManager packageManager = context.getPackageManager();
Resources resources = packageManager.getResourcesForApplication(
iconResource.packageName);
final int id = resources.getIdentifier(iconResource.resourceName, null, null);
icon = resources.getDrawable(id);
} catch (Exception e) {
Log.w(TAG, "Could not load live folder icon: " + extra);
}
}
if (icon == null) {
icon = context.getResources().getDrawable(R.drawable.ic_launcher_folder);
}
final LiveFolderInfo info = new LiveFolderInfo();
info.icon = icon;
info.filtered = filtered;
info.title = name;
info.iconResource = iconResource;
info.uri = data.getData();
info.baseIntent = baseIntent;
info.displayMode = data.getIntExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
LiveFolders.DISPLAY_MODE_GRID);
LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
mFolders.put(info.id, info);
return info;
}
private boolean findSingleSlot(CellLayout.CellInfo cellInfo) {
final int[] xy = new int[2];
if (findSlot(cellInfo, xy, 1, 1)) {
cellInfo.cellX = xy[0];
cellInfo.cellY = xy[1];
return true;
}
return false;
}
private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
boolean[] occupied = mSavedState != null ?
mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
cellInfo = mWorkspace.findAllVacantCells(occupied);
if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
return false;
}
}
return true;
}
private void showNotifications() {
final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
if (statusBar != null) {
statusBar.expand();
}
}
private void startWallpaper() {
closeAllApps(true);
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
Intent chooser = Intent.createChooser(pickWallpaper,
getText(R.string.chooser_wallpaper));
// NOTE: Adds a configure option to the chooser if the wallpaper supports it
// Removed in Eclair MR1
// WallpaperManager wm = (WallpaperManager)
// getSystemService(Context.WALLPAPER_SERVICE);
// WallpaperInfo wi = wm.getWallpaperInfo();
// if (wi != null && wi.getSettingsActivity() != null) {
// LabeledIntent li = new LabeledIntent(getPackageName(),
// R.string.configure_wallpaper, 0);
// li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
// chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
// }
startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
}
/**
* Registers various content observers. The current implementation registers
* only a favorites observer to keep track of the favorites applications.
*/
private void registerContentObservers() {
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
true, mWidgetObserver);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_HOME:
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (SystemProperties.getInt("launcher2.dumpstate", 0) != 0) {
dumpState();
return true;
}
break;
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_HOME:
return true;
}
}
return super.dispatchKeyEvent(event);
}
@Override
public void onBackPressed() {
if (isAllAppsVisible()) {
closeAllApps(true);
} else {
closeFolder();
}
dismissPreview(mPreviousView);
dismissPreview(mNextView);
}
private void closeFolder() {
Folder folder = mWorkspace.getOpenFolder();
if (folder != null) {
closeFolder(folder);
}
}
void closeFolder(Folder folder) {
folder.getInfo().opened = false;
ViewGroup parent = (ViewGroup) folder.getParent();
if (parent != null) {
parent.removeView(folder);
if (folder instanceof DropTarget) {
// Live folders aren't DropTargets.
mDragController.removeDropTarget((DropTarget)folder);
}
}
folder.onClose();
}
/**
* Re-listen when widgets are reset.
*/
private void onAppWidgetReset() {
mAppWidgetHost.startListening();
}
/**
* Go through the and disconnect any of the callbacks in the drawables and the views or we
* leak the previous Home screen on orientation change.
*/
private void unbindDesktopItems() {
for (ItemInfo item: mDesktopItems) {
item.unbind();
}
}
/**
* Launches the intent referred by the clicked shortcut.
*
* @param v The view representing the clicked shortcut.
*/
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ApplicationInfo) {
// Open shortcut
final Intent intent = ((ApplicationInfo) tag).intent;
startActivitySafely(intent);
} else if (tag instanceof FolderInfo) {
handleFolderClick((FolderInfo) tag);
} else if (v == mHandleView) {
if (isAllAppsVisible()) {
closeAllApps(true);
} else {
showAllApps(true);
}
}
}
void startActivitySafely(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} 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(FolderInfo folderInfo) {
if (!folderInfo.opened) {
// Close any open folder
closeFolder();
// Open the requested folder
openFolder(folderInfo);
} else {
// Find the open folder...
Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
int folderScreen;
if (openFolder != null) {
folderScreen = mWorkspace.getScreenForView(openFolder);
// .. and close it
closeFolder(openFolder);
if (folderScreen != mWorkspace.getCurrentScreen()) {
// Close any folder open on the current screen
closeFolder();
// Pull the folder onto this screen
openFolder(folderInfo);
}
}
}
}
/**
* Opens the user fodler 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.
*/
private void openFolder(FolderInfo folderInfo) {
Folder openFolder;
if (folderInfo instanceof UserFolderInfo) {
openFolder = UserFolder.fromXml(this);
} else if (folderInfo instanceof LiveFolderInfo) {
openFolder = com.android.launcher2.LiveFolder.fromXml(this, folderInfo);
} else {
return;
}
openFolder.setDragController(mDragController);
openFolder.setLauncher(this);
openFolder.bind(folderInfo);
folderInfo.opened = true;
mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4);
openFolder.onOpen();
}
public boolean onLongClick(View v) {
switch (v.getId()) {
case R.id.previous_screen:
if (!isAllAppsVisible()) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviousPreview(v);
}
return true;
case R.id.next_screen:
if (!isAllAppsVisible()) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showNextPreview(v);
}
return true;
}
if (isWorkspaceLocked()) {
return false;
}
if (!(v instanceof CellLayout)) {
v = (View) v.getParent();
}
CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
// This happens when long clicking an item with the dpad/trackball
if (cellInfo == null) {
return true;
}
if (mWorkspace.allowLongPress()) {
if (cellInfo.cell == null) {
if (cellInfo.valid) {
// User long pressed on empty space
mWorkspace.setAllowLongPress(false);
showAddDialog(cellInfo);
}
} else {
if (!(cellInfo.cell instanceof Folder)) {
// User long pressed on an item
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
mWorkspace.startDrag(cellInfo);
}
}
}
return true;
}
@SuppressWarnings({"unchecked"})
private void dismissPreview(final View v) {
final PopupWindow window = (PopupWindow) v.getTag();
if (window != null) {
window.setOnDismissListener(new PopupWindow.OnDismissListener() {
public void onDismiss() {
ViewGroup group = (ViewGroup) v.getTag(R.id.workspace);
int count = group.getChildCount();
for (int i = 0; i < count; i++) {
((ImageView) group.getChildAt(i)).setImageDrawable(null);
}
ArrayList<Bitmap> bitmaps = (ArrayList<Bitmap>) v.getTag(R.id.icon);
for (Bitmap bitmap : bitmaps) bitmap.recycle();
v.setTag(R.id.workspace, null);
v.setTag(R.id.icon, null);
window.setOnDismissListener(null);
}
});
window.dismiss();
}
v.setTag(null);
}
private void showPreviousPreview(View anchor) {
int current = mWorkspace.getCurrentScreen();
if (current <= 0) return;
showPreviews(anchor, 0, mWorkspace.getChildCount());
}
private void showNextPreview(View anchor) {
int current = mWorkspace.getCurrentScreen();
if (current >= mWorkspace.getChildCount() - 1) return;
showPreviews(anchor, 0, mWorkspace.getChildCount());
}
private void showPreviews(final View anchor, int start, int end) {
Resources resources = getResources();
Workspace workspace = mWorkspace;
CellLayout cell = ((CellLayout) workspace.getChildAt(start));
float max = workspace.getChildCount();
Rect r = new Rect();
resources.getDrawable(R.drawable.preview_background).getPadding(r);
int extraW = (int) ((r.left + r.right) * max);
int extraH = r.top + r.bottom;
int aW = cell.getWidth() - extraW;
float w = aW / max;
int width = cell.getWidth();
int height = cell.getHeight();
int x = cell.getLeftPadding();
int y = cell.getTopPadding();
width -= (x + cell.getRightPadding());
height -= (y + cell.getBottomPadding());
float scale = w / width;
int count = end - start;
final float sWidth = width * scale;
float sHeight = height * scale;
LinearLayout preview = new LinearLayout(this);
PreviewTouchHandler handler = new PreviewTouchHandler(anchor);
ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(count);
for (int i = start; i < end; i++) {
ImageView image = new ImageView(this);
cell = (CellLayout) workspace.getChildAt(i);
Bitmap bitmap = Bitmap.createBitmap((int) sWidth, (int) sHeight,
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
c.scale(scale, scale);
c.translate(-cell.getLeftPadding(), -cell.getTopPadding());
cell.dispatchDraw(c);
image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background));
image.setImageBitmap(bitmap);
image.setTag(i);
image.setOnClickListener(handler);
image.setOnFocusChangeListener(handler);
image.setFocusable(true);
if (i == mWorkspace.getCurrentScreen()) image.requestFocus();
preview.addView(image,
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
bitmaps.add(bitmap);
}
PopupWindow p = new PopupWindow(this);
p.setContentView(preview);
p.setWidth((int) (sWidth * count + extraW));
p.setHeight((int) (sHeight + extraH));
p.setAnimationStyle(R.style.AnimationPreview);
p.setOutsideTouchable(true);
p.setFocusable(true);
p.setBackgroundDrawable(new ColorDrawable(0));
p.showAsDropDown(anchor, 0, 0);
p.setOnDismissListener(new PopupWindow.OnDismissListener() {
public void onDismiss() {
dismissPreview(anchor);
}
});
anchor.setTag(p);
anchor.setTag(R.id.workspace, preview);
anchor.setTag(R.id.icon, bitmaps);
}
class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener {
private final View mAnchor;
public PreviewTouchHandler(View anchor) {
mAnchor = anchor;
}
public void onClick(View v) {
mWorkspace.snapToScreen((Integer) v.getTag());
v.post(this);
}
public void run() {
dismissPreview(mAnchor);
}
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
mWorkspace.snapToScreen((Integer) v.getTag());
}
}
}
View getDrawerHandle() {
return mHandleView;
}
Workspace getWorkspace() {
return mWorkspace;
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CREATE_SHORTCUT:
return new CreateShortcut().createDialog();
case DIALOG_RENAME_FOLDER:
return new RenameFolder().createDialog();
}
return super.onCreateDialog(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_CREATE_SHORTCUT:
break;
case DIALOG_RENAME_FOLDER:
if (mFolderInfo != null) {
EditText input = (EditText) dialog.findViewById(R.id.folder_name);
final CharSequence text = mFolderInfo.title;
input.setText(text);
input.setSelection(0, text.length());
}
break;
}
}
void showRenameDialog(FolderInfo info) {
mFolderInfo = info;
mWaitingForResult = true;
showDialog(DIALOG_RENAME_FOLDER);
}
private void showAddDialog(CellLayout.CellInfo cellInfo) {
mAddItemCellInfo = cellInfo;
mWaitingForResult = true;
showDialog(DIALOG_CREATE_SHORTCUT);
}
private void pickShortcut(int requestCode, int title) {
Bundle bundle = new Bundle();
ArrayList<String> shortcutNames = new ArrayList<String>();
shortcutNames.add(getString(R.string.group_applications));
bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
R.drawable.ic_launcher_application));
bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
pickIntent.putExtra(Intent.EXTRA_TITLE, getText(title));
pickIntent.putExtras(bundle);
startActivityForResult(pickIntent, requestCode);
}
private class RenameFolder {
private EditText mInput;
Dialog createDialog() {
mWaitingForResult = true;
final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
mInput = (EditText) layout.findViewById(R.id.folder_name);
AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
builder.setIcon(0);
builder.setTitle(getString(R.string.rename_folder_title));
builder.setCancelable(true);
builder.setOnCancelListener(new Dialog.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
cleanup();
}
});
builder.setNegativeButton(getString(R.string.cancel_action),
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
cleanup();
}
}
);
builder.setPositiveButton(getString(R.string.rename_action),
new Dialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
changeFolderName();
}
}
);
builder.setView(layout);
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
public void onShow(DialogInterface dialog) {
mInput.requestFocus();
InputMethodManager inputManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(mInput, 0);
}
});
return dialog;
}
private void changeFolderName() {
final String name = mInput.getText().toString();
if (!TextUtils.isEmpty(name)) {
// Make sure we have the right folder info
mFolderInfo = mFolders.get(mFolderInfo.id);
mFolderInfo.title = name;
LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
if (mWorkspaceLoading) {
lockAllApps();
mModel.setWorkspaceDirty();
mModel.startLoader(Launcher.this, false);
} else {
final FolderIcon folderIcon = (FolderIcon)
mWorkspace.getViewForTag(mFolderInfo);
if (folderIcon != null) {
folderIcon.setText(name);
getWorkspace().requestLayout();
} else {
lockAllApps();
mModel.setWorkspaceDirty();
mWorkspaceLoading = true;
mModel.startLoader(Launcher.this, false);
}
}
}
cleanup();
}
private void cleanup() {
dismissDialog(DIALOG_RENAME_FOLDER);
mWaitingForResult = false;
mFolderInfo = null;
}
}
boolean isAllAppsVisible() {
return mAllAppsGrid.isVisible();
}
boolean isAllAppsOpaque() {
return mAllAppsGrid.isOpaque();
}
void showAllApps(boolean animated) {
mAllAppsGrid.zoom(1.0f, animated);
//mWorkspace.hide();
mWorkspace.startFading(false);
mAllAppsGrid.setFocusable(true);
mAllAppsGrid.requestFocus();
// TODO: fade these two too
mDeleteZone.setVisibility(View.GONE);
//mHandleView.setVisibility(View.GONE);
}
void closeAllApps(boolean animated) {
if (mAllAppsGrid.isVisible()) {
mAllAppsGrid.zoom(0.0f, animated);
mAllAppsGrid.setFocusable(false);
mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
mWorkspace.startFading(true);
// TODO: fade these two too
/*
mDeleteZone.setVisibility(View.VISIBLE);
mHandleView.setVisibility(View.VISIBLE);
*/
}
}
void lockAllApps() {
// TODO
}
void unlockAllApps() {
// TODO
}
/**
* Displays the shortcut creation dialog and launches, if necessary, the
* appropriate activity.
*/
private class CreateShortcut implements DialogInterface.OnClickListener,
DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
DialogInterface.OnShowListener {
private AddAdapter mAdapter;
Dialog createDialog() {
mWaitingForResult = true;
mAdapter = new AddAdapter(Launcher.this);
final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
builder.setTitle(getString(R.string.menu_item_add_item));
builder.setAdapter(mAdapter, this);
builder.setInverseBackgroundForced(true);
AlertDialog dialog = builder.create();
dialog.setOnCancelListener(this);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
return dialog;
}
public void onCancel(DialogInterface dialog) {
mWaitingForResult = false;
cleanup();
}
public void onDismiss(DialogInterface dialog) {
}
private void cleanup() {
try {
dismissDialog(DIALOG_CREATE_SHORTCUT);
} catch (Exception e) {
// An exception is thrown if the dialog is not visible, which is fine
}
}
/**
* Handle the action clicked in the "Add to home" dialog.
*/
public void onClick(DialogInterface dialog, int which) {
Resources res = getResources();
cleanup();
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
// Insert extra item to handle picking application
pickShortcut(REQUEST_PICK_SHORTCUT, R.string.title_select_shortcut);
break;
}
case AddAdapter.ITEM_APPWIDGET: {
int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// add the search widget
ArrayList<AppWidgetProviderInfo> customInfo =
new ArrayList<AppWidgetProviderInfo>();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = new ComponentName(getPackageName(), "XXX.YYY");
info.label = getString(R.string.group_search);
info.icon = R.drawable.ic_search_widget;
customInfo.add(info);
pickIntent.putParcelableArrayListExtra(
AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo);
ArrayList<Bundle> customExtras = new ArrayList<Bundle>();
Bundle b = new Bundle();
b.putString(EXTRA_CUSTOM_WIDGET, SEARCH_WIDGET);
customExtras.add(b);
pickIntent.putParcelableArrayListExtra(
AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras);
// start the pick activity
startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
break;
}
case AddAdapter.ITEM_LIVE_FOLDER: {
// Insert extra item to handle inserting folder
Bundle bundle = new Bundle();
ArrayList<String> shortcutNames = new ArrayList<String>();
shortcutNames.add(res.getString(R.string.group_folder));
bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
ArrayList<ShortcutIconResource> shortcutIcons =
new ArrayList<ShortcutIconResource>();
shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
R.drawable.ic_launcher_folder));
bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT,
new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER));
pickIntent.putExtra(Intent.EXTRA_TITLE,
getText(R.string.title_select_live_folder));
pickIntent.putExtras(bundle);
startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
break;
}
case AddAdapter.ITEM_WALLPAPER: {
startWallpaper();
break;
}
}
}
public void onShow(DialogInterface dialog) {
}
}
/**
* Receives notifications when applications are added/removed.
*/
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();
}
}
/**
* Implementation of the method from LauncherModel.Callbacks.
*/
public int getCurrentWorkspaceScreen() {
return mWorkspace.getCurrentScreen();
}
/**
* Refreshes the shortcuts shown on the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
final Workspace workspace = mWorkspace;
int count = workspace.getChildCount();
for (int i = 0; i < count; i++) {
// Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
}
if (DEBUG_USER_INTERFACE) {
android.widget.Button finishButton = new android.widget.Button(this);
finishButton.setText("Finish");
workspace.addInScreen(finishButton, 1, 0, 0, 1, 1);
finishButton.setOnClickListener(new android.widget.Button.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}
}
/**
* Bind the items start-end from the list.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
final Workspace workspace = mWorkspace;
for (int i=start; i<end; i++) {
final ItemInfo item = shortcuts.get(i);
mDesktopItems.add(item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
final View shortcut = createShortcut((ApplicationInfo) item);
workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
false);
break;
case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
(UserFolderInfo) item);
workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
false);
break;
case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
R.layout.live_folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
(LiveFolderInfo) item);
workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
false);
break;
case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
final int screen = workspace.getCurrentScreen();
final View view = mInflater.inflate(R.layout.widget_search,
(ViewGroup) workspace.getChildAt(screen), false);
Search search = (Search) view.findViewById(R.id.widget_search);
search.setLauncher(this);
final Widget widget = (Widget) item;
view.setTag(widget);
workspace.addWidget(view, widget, false);
break;
}
}
workspace.requestLayout();
}
/**
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindFolders(HashMap<Long, FolderInfo> folders) {
mFolders.clear();
mFolders.putAll(folders);
}
/**
* Add the views for a widget to the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppWidget(LauncherAppWidgetInfo item) {
final Workspace workspace = mWorkspace;
final int appWidgetId = item.appWidgetId;
final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
item.hostView.setTag(item);
workspace.addInScreen(item.hostView, item.screen, item.cellX,
item.cellY, item.spanX, item.spanY, false);
workspace.requestLayout();
mDesktopItems.add(item);
}
/**
* Callback saying that there aren't any more items to bind.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems() {
if (mSavedState != null) {
if (!mWorkspace.hasFocus()) {
mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
}
final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
if (userFolders != null) {
for (long folderId : userFolders) {
final FolderInfo info = mFolders.get(folderId);
if (info != null) {
openFolder(info);
}
}
final Folder openFolder = mWorkspace.getOpenFolder();
if (openFolder != null) {
openFolder.requestFocus();
}
}
mSavedState = null;
}
if (mSavedInstanceState != null) {
super.onRestoreInstanceState(mSavedInstanceState);
mSavedInstanceState = null;
}
mWorkspaceLoading = false;
}
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(ArrayList<ApplicationInfo> apps) {
mAllAppsGrid.setApps(apps);
}
/**
* A package was installed.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindPackageAdded(ArrayList<ApplicationInfo> apps) {
removeDialog(DIALOG_CREATE_SHORTCUT);
mAllAppsGrid.addApps(apps);
}
/**
* A package was updated.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps) {
removeDialog(DIALOG_CREATE_SHORTCUT);
mWorkspace.updateShortcutsForPackage(packageName);
}
/**
* A package was uninstalled.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps) {
removeDialog(DIALOG_CREATE_SHORTCUT);
mWorkspace.removeShortcutsForPackage(packageName);
mAllAppsGrid.removeApps(apps);
}
/**
* 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, "mIsNewIntent=" + mIsNewIntent);
Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
Log.d(TAG, "mRestoring=" + mRestoring);
Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
Log.d(TAG, "mDesktopItems.size=" + mDesktopItems.size());
Log.d(TAG, "mFolders.size=" + mFolders.size());
mModel.dumpState();
mAllAppsGrid.dumpState();
Log.d(TAG, "END launcher2 dump state");
}
}