blob: 96569832b5acddd31e67f3d971d76ba0889d6e4b [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Application;
22import android.app.Dialog;
23import android.app.SearchManager;
24import android.app.StatusBarManager;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ComponentName;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.SharedPreferences;
34import android.content.pm.ActivityInfo;
35import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.res.Resources;
38import android.content.res.Configuration;
39import android.database.ContentObserver;
40import android.gadget.GadgetProviderInfo;
41import android.gadget.GadgetManager;
42import android.graphics.Bitmap;
43import android.graphics.Rect;
44import android.graphics.drawable.BitmapDrawable;
45import android.graphics.drawable.Drawable;
46import android.graphics.drawable.TransitionDrawable;
47import android.net.Uri;
48import android.os.Bundle;
49import android.os.Handler;
50import android.os.IBinder;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.Message;
55import android.provider.*;
56import android.telephony.PhoneNumberUtils;
57import android.text.Selection;
58import android.text.SpannableStringBuilder;
59import android.text.TextUtils;
60import android.text.method.TextKeyListener;
61import android.util.Log;
62import android.view.Display;
63import android.view.Gravity;
64import android.view.KeyEvent;
65import android.view.LayoutInflater;
66import android.view.Menu;
67import android.view.MenuItem;
68import android.view.View;
69import android.view.ViewGroup;
70import android.view.WindowManager;
71import android.view.View.OnLongClickListener;
72import android.view.inputmethod.InputMethodManager;
73import android.widget.AdapterView;
74import android.widget.EditText;
75import android.widget.ListView;
76import android.widget.TextView;
77import android.widget.Toast;
78import android.widget.GridView;
79import android.widget.SlidingDrawer;
80import android.app.IWallpaperService;
81
82import java.lang.ref.WeakReference;
83import java.util.ArrayList;
84
85/**
86 * Default launcher application.
87 */
88public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener {
89 static final String LOG_TAG = "Launcher";
90 static final boolean LOGD = false;
91
92 private static final boolean PROFILE_STARTUP = false;
93 private static final boolean DEBUG_USER_INTERFACE = false;
94
95 private static final int WALLPAPER_SCREENS_SPAN = 2;
96
97 private static final int MENU_GROUP_ADD = 1;
98 private static final int MENU_ADD = Menu.FIRST + 1;
99 private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
100 private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
101 private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
102 private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
103
104 private static final int REQUEST_CREATE_SHORTCUT = 1;
105 private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
106 private static final int REQUEST_CREATE_GADGET = 5;
107 private static final int REQUEST_PICK_APPLICATION = 6;
108 private static final int REQUEST_PICK_SHORTCUT = 7;
109 private static final int REQUEST_PICK_LIVE_FOLDER = 8;
110 private static final int REQUEST_PICK_GADGET = 9;
111
112 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
113
114 static final int SCREEN_COUNT = 3;
115 static final int DEFAULT_SCREN = 1;
116 static final int NUMBER_CELLS_X = 4;
117 static final int NUMBER_CELLS_Y = 4;
118
119 private static final int DIALOG_CREATE_SHORTCUT = 1;
120 static final int DIALOG_RENAME_FOLDER = 2;
121
122 private static final String PREFERENCES = "launcher";
123 private static final String KEY_LOCALE = "locale";
124 private static final String KEY_MCC = "mcc";
125 private static final String KEY_MNC = "mnc";
126
127 // Type: int
128 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
129 // Type: boolean
130 private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder";
131 // Type: long
132 private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
133 // Type: int
134 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
135 // Type: int
136 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX";
137 // Type: int
138 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
139 // Type: int
140 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX";
141 // Type: int
142 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY";
143 // Type: int
144 private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX";
145 // Type: int
146 private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY";
147 // Type: int[]
148 private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells";
149 // Type: boolean
150 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
151 // Type: long
152 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
153
The Android Open Source Projectbc219c32009-03-09 11:52:14 -0700154 private static final LauncherModel sModel = new LauncherModel();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800155
156 private static Bitmap sWallpaper;
157
158 private static final Object sLock = new Object();
159 private static int sScreen = DEFAULT_SCREN;
160
161 private static WallpaperIntentReceiver sWallpaperReceiver;
162
163 private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
164 private final ContentObserver mObserver = new FavoritesChangeObserver();
165
166 private LayoutInflater mInflater;
167
168 private DragLayer mDragLayer;
169 private Workspace mWorkspace;
170
171 private GadgetManager mGadgetManager;
172 private LauncherGadgetHost mGadgetHost;
173
174 static final int GADGET_HOST_ID = 1024;
175
176 private CellLayout.CellInfo mAddItemCellInfo;
177 private CellLayout.CellInfo mMenuAddInfo;
178 private final int[] mCellCoordinates = new int[2];
179 private FolderInfo mFolderInfo;
180
181 private SlidingDrawer mDrawer;
182 private TransitionDrawable mHandleIcon;
183 private AllAppsGridView mAllAppsGrid;
184
185 private boolean mDesktopLocked = true;
186 private Bundle mSavedState;
187
188 private SpannableStringBuilder mDefaultKeySsb = null;
189
190 private boolean mDestroyed;
191
192 private boolean mRestoring;
193 private boolean mWaitingForResult;
194 private boolean mLocaleChanged;
195
196 private Bundle mSavedInstanceState;
197
198 @Override
199 protected void onCreate(Bundle savedInstanceState) {
200 super.onCreate(savedInstanceState);
201 mInflater = getLayoutInflater();
202
203 mGadgetManager = GadgetManager.getInstance(this);
204
205 mGadgetHost = new LauncherGadgetHost(this, GADGET_HOST_ID);
206 mGadgetHost.startListening();
207
208 if (PROFILE_STARTUP) {
209 android.os.Debug.startMethodTracing("/sdcard/launcher");
210 }
211
212 checkForLocaleChange();
213 setWallpaperDimension();
214
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800215 setContentView(R.layout.launcher);
216 setupViews();
217
218 registerIntentReceivers();
219 registerContentObservers();
220
221 mSavedState = savedInstanceState;
222 restoreState(mSavedState);
223
224 if (PROFILE_STARTUP) {
225 android.os.Debug.stopMethodTracing();
226 }
227
228 if (!mRestoring) {
229 startLoaders();
230 }
231
232 // For handling default keys
233 mDefaultKeySsb = new SpannableStringBuilder();
234 Selection.setSelection(mDefaultKeySsb, 0);
235 }
236
237 private void checkForLocaleChange() {
238 final SharedPreferences preferences = getSharedPreferences(PREFERENCES, MODE_PRIVATE);
239 final Configuration configuration = getResources().getConfiguration();
240
241 final String previousLocale = preferences.getString(KEY_LOCALE, null);
242 final String locale = configuration.locale.toString();
243
244 final int previousMcc = preferences.getInt(KEY_MCC, -1);
245 final int mcc = configuration.mcc;
246
247 final int previousMnc = preferences.getInt(KEY_MNC, -1);
248 final int mnc = configuration.mnc;
249
250 mLocaleChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
251
252 if (mLocaleChanged) {
253 final SharedPreferences.Editor editor = preferences.edit();
254 editor.putString(KEY_LOCALE, locale);
255 editor.putInt(KEY_MCC, mcc);
256 editor.putInt(KEY_MNC, mnc);
257 editor.commit();
258 }
259 }
260
261 static int getScreen() {
262 synchronized (sLock) {
263 return sScreen;
264 }
265 }
266
267 static void setScreen(int screen) {
268 synchronized (sLock) {
269 sScreen = screen;
270 }
271 }
272
273 private void startLoaders() {
274 sModel.loadApplications(true, this, mLocaleChanged);
275 sModel.loadUserItems(!mLocaleChanged, this, mLocaleChanged, true);
276 mRestoring = false;
277 }
278
279 private void setWallpaperDimension() {
280 IBinder binder = ServiceManager.getService(WALLPAPER_SERVICE);
281 IWallpaperService wallpaperService = IWallpaperService.Stub.asInterface(binder);
282
283 Display display = getWindowManager().getDefaultDisplay();
284 boolean isPortrait = display.getWidth() < display.getHeight();
285
286 final int width = isPortrait ? display.getWidth() : display.getHeight();
287 final int height = isPortrait ? display.getHeight() : display.getWidth();
288 try {
289 wallpaperService.setDimensionHints(width * WALLPAPER_SCREENS_SPAN, height);
290 } catch (RemoteException e) {
291 // System is dead!
292 }
293 }
294
295 @Override
296 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
297 // The pattern used here is that a user PICKs a specific application,
298 // which, depending on the target, might need to CREATE the actual target.
299
300 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
301 // launch over to the Music app to actually CREATE_SHORTCUT.
302
303 if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
304 switch (requestCode) {
305 case REQUEST_PICK_APPLICATION:
306 completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked);
307 break;
308 case REQUEST_PICK_SHORTCUT:
309 addShortcut(data);
310 break;
311 case REQUEST_CREATE_SHORTCUT:
312 completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked);
313 break;
314 case REQUEST_PICK_LIVE_FOLDER:
315 addLiveFolder(data);
316 break;
317 case REQUEST_CREATE_LIVE_FOLDER:
318 completeAddLiveFolder(data, mAddItemCellInfo, !mDesktopLocked);
319 break;
320 case REQUEST_PICK_GADGET:
321 addGadget(data);
322 break;
323 case REQUEST_CREATE_GADGET:
324 completeAddGadget(data, mAddItemCellInfo, !mDesktopLocked);
325 break;
326 }
327 } else if (requestCode == REQUEST_PICK_GADGET &&
328 resultCode == RESULT_CANCELED && data != null) {
329 // Clean up the gadgetId if we canceled
330 int gadgetId = data.getIntExtra(GadgetManager.EXTRA_GADGET_ID, -1);
331 if (gadgetId != -1) {
332 mGadgetHost.deleteGadgetId(gadgetId);
333 }
334 }
335 mWaitingForResult = false;
336 }
337
338 @Override
339 protected void onResume() {
340 super.onResume();
341
342 if (mRestoring) {
343 startLoaders();
344 }
345 }
346
347 @Override
348 public boolean onKeyUp(int keyCode, KeyEvent event) {
349 boolean handled = super.onKeyUp(keyCode, event);
350 if (keyCode == KeyEvent.KEYCODE_SEARCH) {
351 handled = mWorkspace.snapToSearch();
352 if (handled) closeDrawer(true);
353 }
354 return handled;
355 }
356
357 private boolean acceptFilter() {
358 final InputMethodManager inputManager = (InputMethodManager)
359 getSystemService(Context.INPUT_METHOD_SERVICE);
360 return !inputManager.isFullscreenMode();
361 }
362
363 @Override
364 public boolean onKeyDown(int keyCode, KeyEvent event) {
365 boolean handled = super.onKeyDown(keyCode, event);
366 if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) {
367 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
368 keyCode, event);
369 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
370 // something usable has been typed - dispatch it now.
371 final String str = mDefaultKeySsb.toString();
372
373 boolean isDialable = true;
374 final int count = str.length();
375 for (int i = 0; i < count; i++) {
376 if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
377 isDialable = false;
378 break;
379 }
380 }
381 Intent intent;
382 if (isDialable) {
383 intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
384 } else {
385 intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
386 intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
387 }
388
389 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
390
391 try {
392 startActivity(intent);
393 } catch (android.content.ActivityNotFoundException ex) {
394 // Oh well... no one knows how to filter/dial. Life goes on.
395 }
396
397 mDefaultKeySsb.clear();
398 mDefaultKeySsb.clearSpans();
399 Selection.setSelection(mDefaultKeySsb, 0);
400
401 return true;
402 }
403 }
404
405 return handled;
406 }
407
408 /**
409 * Restores the previous state, if it exists.
410 *
411 * @param savedState The previous state.
412 */
413 private void restoreState(Bundle savedState) {
414 if (savedState == null) {
415 return;
416 }
417
418 final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
419 if (currentScreen > -1) {
420 mWorkspace.setCurrentScreen(currentScreen);
421 }
422
423 final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
424 if (addScreen > -1) {
425 mAddItemCellInfo = new CellLayout.CellInfo();
426 final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
427 addItemCellInfo.valid = true;
428 addItemCellInfo.screen = addScreen;
429 addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
430 addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
431 addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
432 addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
433 addItemCellInfo.findVacantCellsFromOccupied(
434 savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS),
435 savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X),
436 savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y));
437 mRestoring = true;
438 }
439
440 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
441 if (renameFolder) {
442 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
443 mFolderInfo = sModel.getFolderById(this, id);
444 mRestoring = true;
445 }
446 }
447
448 /**
449 * Finds all the views we need and configure them properly.
450 */
451 private void setupViews() {
452 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
453 final DragLayer dragLayer = mDragLayer;
454
455 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
456 final Workspace workspace = mWorkspace;
457
458 mDrawer = (SlidingDrawer) dragLayer.findViewById(R.id.drawer);
459 final SlidingDrawer drawer = mDrawer;
460
461 mAllAppsGrid = (AllAppsGridView) drawer.getContent();
462 final AllAppsGridView grid = mAllAppsGrid;
463
464 final DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
465
466 final HandleView handleIcon = (HandleView) drawer.findViewById(R.id.all_apps);
467 handleIcon.setLauncher(this);
468 mHandleIcon = (TransitionDrawable) handleIcon.getDrawable();
469 mHandleIcon.setCrossFadeEnabled(true);
470
471 drawer.lock();
472 final DrawerManager drawerManager = new DrawerManager();
473 drawer.setOnDrawerOpenListener(drawerManager);
474 drawer.setOnDrawerCloseListener(drawerManager);
475 drawer.setOnDrawerScrollListener(drawerManager);
476
477 grid.setTextFilterEnabled(true);
478 grid.setDragger(dragLayer);
479 grid.setLauncher(this);
480
481 workspace.setOnLongClickListener(this);
482 workspace.setDragger(dragLayer);
483 workspace.setLauncher(this);
484 loadWallpaper();
485
486 deleteZone.setLauncher(this);
487 deleteZone.setDragController(dragLayer);
488 deleteZone.setHandle(handleIcon);
489
490 dragLayer.setIgnoredDropTarget(grid);
491 dragLayer.setDragScoller(workspace);
492 dragLayer.setDragListener(deleteZone);
493 }
494
495 /**
496 * Creates a view representing a shortcut.
497 *
498 * @param info The data structure describing the shortcut.
499 *
500 * @return A View inflated from R.layout.application.
501 */
502 View createShortcut(ApplicationInfo info) {
503 return createShortcut(R.layout.application,
504 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
505 }
506
507 /**
508 * Creates a view representing a shortcut inflated from the specified resource.
509 *
510 * @param layoutResId The id of the XML layout used to create the shortcut.
511 * @param parent The group the shortcut belongs to.
512 * @param info The data structure describing the shortcut.
513 *
514 * @return A View inflated from layoutResId.
515 */
516 View createShortcut(int layoutResId, ViewGroup parent, ApplicationInfo info) {
517 TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
518
519 if (!info.filtered) {
520 info.icon = Utilities.createIconThumbnail(info.icon, this);
521 info.filtered = true;
522 }
523
524 favorite.setCompoundDrawablesWithIntrinsicBounds(null, info.icon, null, null);
525 favorite.setText(info.title);
526 favorite.setTag(info);
527 favorite.setOnClickListener(this);
528
529 return favorite;
530 }
531
532 /**
533 * Add an application shortcut to the workspace.
534 *
535 * @param data The intent describing the application.
536 * @param cellInfo The position on screen where to create the shortcut.
537 */
538 void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo,
539 boolean insertAtFirst) {
540 cellInfo.screen = mWorkspace.getCurrentScreen();
541 if (!findSingleSlot(cellInfo)) return;
542
543 // Find details for this application
544 ComponentName component = data.getComponent();
545 PackageManager packageManager = context.getPackageManager();
546 ActivityInfo activityInfo = null;
547 try {
548 activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */);
549 } catch (NameNotFoundException e) {
550 Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
551 }
552
553 if (activityInfo != null) {
554 ApplicationInfo itemInfo = new ApplicationInfo();
555
556 itemInfo.title = activityInfo.loadLabel(packageManager);
557 if (itemInfo.title == null) {
558 itemInfo.title = activityInfo.name;
559 }
560
561 itemInfo.setActivity(component, Intent.FLAG_ACTIVITY_NEW_TASK |
562 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
563 itemInfo.icon = activityInfo.loadIcon(packageManager);
564 itemInfo.container = ItemInfo.NO_ID;
565
566 mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst);
567 }
568 }
569
570 /**
571 * Add a shortcut to the workspace.
572 *
573 * @param data The intent describing the shortcut.
574 * @param cellInfo The position on screen where to create the shortcut.
575 * @param insertAtFirst
576 */
577 private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo,
578 boolean insertAtFirst) {
579 cellInfo.screen = mWorkspace.getCurrentScreen();
580 if (!findSingleSlot(cellInfo)) return;
581
582 final ApplicationInfo info = addShortcut(this, data, cellInfo, false);
583
584 if (!mRestoring) {
585 sModel.addDesktopItem(info);
586
587 final View view = createShortcut(info);
588 mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
589 } else if (sModel.isDesktopLoaded()) {
590 sModel.addDesktopItem(info);
591 }
592 }
593
594
595 /**
596 * Add a gadget to the workspace.
597 *
598 * @param data The intent describing the gadgetId.
599 * @param cellInfo The position on screen where to create the shortcut.
600 * @param insertAtFirst
601 */
602 private void completeAddGadget(Intent data, CellLayout.CellInfo cellInfo,
603 boolean insertAtFirst) {
604
605 Bundle extras = data.getExtras();
606 int gadgetId = extras.getInt(GadgetManager.EXTRA_GADGET_ID, -1);
607
608 Log.d(LOG_TAG, "dumping extras content="+extras.toString());
609
610 GadgetProviderInfo gadgetInfo = mGadgetManager.getGadgetInfo(gadgetId);
611
612 // Calculate the grid spans needed to fit this gadget
613 CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
614 int[] spans = layout.rectToCell(gadgetInfo.minWidth, gadgetInfo.minHeight);
615
616 // Try finding open space on Launcher screen
617 final int[] xy = mCellCoordinates;
618 if (!findSlot(cellInfo, xy, spans[0], spans[1])) return;
619
620 // Build Launcher-specific Gadget info and save to database
621 LauncherGadgetInfo launcherInfo = new LauncherGadgetInfo(gadgetId);
622 launcherInfo.spanX = spans[0];
623 launcherInfo.spanY = spans[1];
624
625 LauncherModel.addItemToDatabase(this, launcherInfo,
626 LauncherSettings.Favorites.CONTAINER_DESKTOP,
627 mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
628
629 if (!mRestoring) {
630 sModel.addDesktopGadget(launcherInfo);
631
632 // Perform actual inflation because we're live
633 launcherInfo.hostView = mGadgetHost.createView(this, gadgetId, gadgetInfo);
634
635 launcherInfo.hostView.setGadget(gadgetId, gadgetInfo);
636 launcherInfo.hostView.setTag(launcherInfo);
637
638 mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
639 launcherInfo.spanX, launcherInfo.spanY, insertAtFirst);
640 } else if (sModel.isDesktopLoaded()) {
641 sModel.addDesktopGadget(launcherInfo);
642 }
643 }
644
645 public LauncherGadgetHost getGadgetHost() {
646 return mGadgetHost;
647 }
648
649 static ApplicationInfo addShortcut(Context context, Intent data,
650 CellLayout.CellInfo cellInfo, boolean notify) {
651
652 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
653 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
654 Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
655
656 Drawable icon = null;
657 boolean filtered = false;
658 boolean customIcon = false;
659 Intent.ShortcutIconResource iconResource = null;
660
661 if (bitmap != null) {
662 icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
663 filtered = true;
664 customIcon = true;
665 } else {
666 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
667 if (extra != null && extra instanceof Intent.ShortcutIconResource) {
668 try {
669 iconResource = (Intent.ShortcutIconResource) extra;
670 final PackageManager packageManager = context.getPackageManager();
671 Resources resources = packageManager.getResourcesForApplication(
672 iconResource.packageName);
673 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
674 icon = resources.getDrawable(id);
675 } catch (Exception e) {
676 Log.w(LOG_TAG, "Could not load shortcut icon: " + extra);
677 }
678 }
679 }
680
681 if (icon == null) {
682 icon = context.getPackageManager().getDefaultActivityIcon();
683 }
684
685 final ApplicationInfo info = new ApplicationInfo();
686 info.icon = icon;
687 info.filtered = filtered;
688 info.title = name;
689 info.intent = intent;
690 info.customIcon = customIcon;
691 info.iconResource = iconResource;
692
693 LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
694 cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
695 return info;
696 }
697
698 @Override
699 protected void onNewIntent(Intent intent) {
700 super.onNewIntent(intent);
701
702 // Close the menu
703 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
704 getWindow().closeAllPanels();
705
706 try {
707 dismissDialog(DIALOG_CREATE_SHORTCUT);
708 // Unlock the workspace if the dialog was showing
709 mWorkspace.unlock();
710 } catch (Exception e) {
711 // An exception is thrown if the dialog is not visible, which is fine
712 }
713
714 try {
715 dismissDialog(DIALOG_RENAME_FOLDER);
716 // Unlock the workspace if the dialog was showing
717 mWorkspace.unlock();
718 } catch (Exception e) {
719 // An exception is thrown if the dialog is not visible, which is fine
720 }
721
722 // If we are already in front we go back to the default screen,
723 // otherwise we don't
724 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) !=
725 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
726 if (!mWorkspace.isDefaultScreenShowing()) {
727 mWorkspace.moveToDefaultScreen();
728 }
729 closeDrawer();
730 View v = getWindow().peekDecorView();
731 if (v != null && v.getWindowToken() != null) {
732 InputMethodManager imm = (InputMethodManager)getSystemService(
733 INPUT_METHOD_SERVICE);
734 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
735 }
736 } else {
737 closeDrawer(false);
738 }
739 }
740 }
741
742 @Override
743 protected void onRestoreInstanceState(Bundle savedInstanceState) {
744 // Do not call super here
745 mSavedInstanceState = savedInstanceState;
746 }
747
748 @Override
749 protected void onSaveInstanceState(Bundle outState) {
750 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen());
751
752 final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
753 if (folders.size() > 0) {
754 final int count = folders.size();
755 long[] ids = new long[count];
756 for (int i = 0; i < count; i++) {
757 final FolderInfo info = folders.get(i).getInfo();
758 ids[i] = info.id;
759 }
760 outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids);
761 } else {
762 super.onSaveInstanceState(outState);
763 }
764
765 if (mDrawer.isOpened()) {
766 outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
767 }
768
769 if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
770 final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
771 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen);
772
773 outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen);
774 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX);
775 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY);
776 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX);
777 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY);
778 outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX());
779 outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY());
780 outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS,
781 layout.getOccupiedCells());
782 }
783
784 if (mFolderInfo != null && mWaitingForResult) {
785 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
786 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
787 }
788 }
789
790 @Override
791 public void onDestroy() {
792 mDestroyed = true;
793
794 super.onDestroy();
795
796 try {
797 mGadgetHost.stopListening();
798 } catch (NullPointerException ex) {
799 Log.w(LOG_TAG, "problem while stopping GadgetHost during Launcher destruction", ex);
800 }
801
802 TextKeyListener.getInstance().release();
803
804 mAllAppsGrid.clearTextFilter();
805 mAllAppsGrid.setAdapter(null);
806 sModel.unbind();
807 sModel.abortLoaders();
808
809 getContentResolver().unregisterContentObserver(mObserver);
810 unregisterReceiver(mApplicationsReceiver);
811 }
812
813 @Override
814 public void startActivityForResult(Intent intent, int requestCode) {
815 mWaitingForResult = true;
816 super.startActivityForResult(intent, requestCode);
817 }
818
819 @Override
820 public void startSearch(String initialQuery, boolean selectInitialQuery,
821 Bundle appSearchData, boolean globalSearch) {
822 if (appSearchData == null) {
823 appSearchData = new Bundle();
824 appSearchData.putString(SearchManager.SOURCE, "launcher-search");
825 }
826 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
827 }
828
829 @Override
830 public boolean onCreateOptionsMenu(Menu menu) {
831 if (mDesktopLocked) return false;
832
833 super.onCreateOptionsMenu(menu);
834 menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
835 .setIcon(android.R.drawable.ic_menu_add)
836 .setAlphabeticShortcut('A');
837 menu.add(0, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
838 .setIcon(android.R.drawable.ic_menu_gallery)
839 .setAlphabeticShortcut('W');
840 menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
841 .setIcon(android.R.drawable.ic_search_category_default)
842 .setAlphabeticShortcut(SearchManager.MENU_KEY);
843 menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications)
844 .setIcon(com.android.internal.R.drawable.ic_menu_notifications)
845 .setAlphabeticShortcut('N');
846
847 final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
848 settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
849 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
850
851 menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
852 .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
853 .setIntent(settings);
854
855 return true;
856 }
857
858 @Override
859 public boolean onPrepareOptionsMenu(Menu menu) {
860 super.onPrepareOptionsMenu(menu);
861
862 mMenuAddInfo = mWorkspace.findAllVacantCells(null);
863 menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid);
864
865 return true;
866 }
867
868 @Override
869 public boolean onOptionsItemSelected(MenuItem item) {
870 switch (item.getItemId()) {
871 case MENU_ADD:
872 addItems();
873 return true;
874 case MENU_WALLPAPER_SETTINGS:
875 startWallpaper();
876 return true;
877 case MENU_SEARCH:
878 if (mWorkspace.snapToSearch()) {
879 closeDrawer(true); // search gadget: get drawer out of the way
880 } else {
881 onSearchRequested(); // no search gadget: use system search UI
882 }
883 return true;
884 case MENU_NOTIFICATIONS:
885 showNotifications();
886 return true;
887 }
888
889 return super.onOptionsItemSelected(item);
890 }
891
892 private void addItems() {
893 showAddDialog(mMenuAddInfo);
894 }
895
896 private void removeShortcutsForPackage(String packageName) {
897 if (packageName != null && packageName.length() > 0) {
898 mWorkspace.removeShortcutsForPackage(packageName);
899 }
900 }
901
902 void addGadget(Intent data) {
903 // TODO: catch bad gadget exception when sent
904 int gadgetId = data.getIntExtra(GadgetManager.EXTRA_GADGET_ID, -1);
905 GadgetProviderInfo gadget = mGadgetManager.getGadgetInfo(gadgetId);
906
907 if (gadget.configure != null) {
908 // Launch over to configure gadget, if needed
909 Intent intent = new Intent(GadgetManager.ACTION_GADGET_CONFIGURE);
910 intent.setComponent(gadget.configure);
911 intent.putExtra(GadgetManager.EXTRA_GADGET_ID, gadgetId);
912
913 startActivityForResult(intent, REQUEST_CREATE_GADGET);
914 } else {
915 // Otherwise just add it
916 onActivityResult(REQUEST_CREATE_GADGET, Activity.RESULT_OK, data);
917 }
918 }
919
920 void addSearch() {
921 final Widget info = Widget.makeSearch();
922 final CellLayout.CellInfo cellInfo = mAddItemCellInfo;
923
924 final int[] xy = mCellCoordinates;
925 final int spanX = info.spanX;
926 final int spanY = info.spanY;
927
928 if (!findSlot(cellInfo, xy, spanX, spanY)) return;
929
930 sModel.addDesktopItem(info);
931 LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
932 mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
933
934 final View view = mInflater.inflate(info.layoutResource, null);
935 view.setTag(info);
936
937 mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
938 }
939
940 void addShortcut(Intent intent) {
941 startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
942 }
943
944 void addLiveFolder(Intent intent) {
945 startActivityForResult(intent, REQUEST_CREATE_LIVE_FOLDER);
946 }
947
948 void addFolder(boolean insertAtFirst) {
949 UserFolderInfo folderInfo = new UserFolderInfo();
950 folderInfo.title = getText(R.string.folder_name);
951
952 CellLayout.CellInfo cellInfo = mAddItemCellInfo;
953 cellInfo.screen = mWorkspace.getCurrentScreen();
954 if (!findSingleSlot(cellInfo)) return;
955
956 // Update the model
957 LauncherModel.addItemToDatabase(this, folderInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP,
958 mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false);
959 sModel.addDesktopItem(folderInfo);
960 sModel.addFolder(folderInfo);
961
962 // Create the view
963 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
964 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo);
965 mWorkspace.addInCurrentScreen(newFolder,
966 cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
967 }
968
969 private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo,
970 boolean insertAtFirst) {
971 cellInfo.screen = mWorkspace.getCurrentScreen();
972 if (!findSingleSlot(cellInfo)) return;
973
974 final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false);
975
976 if (!mRestoring) {
977 sModel.addDesktopItem(info);
978
979 final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this,
980 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
981 mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
982 } else if (sModel.isDesktopLoaded()) {
983 sModel.addDesktopItem(info);
984 }
985 }
986
987 static LiveFolderInfo addLiveFolder(Context context, Intent data,
988 CellLayout.CellInfo cellInfo, boolean notify) {
989
990 Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT);
991 String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME);
992
993 Drawable icon = null;
994 boolean filtered = false;
995 Intent.ShortcutIconResource iconResource = null;
996
997 Parcelable extra = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON);
998 if (extra != null && extra instanceof Intent.ShortcutIconResource) {
999 try {
1000 iconResource = (Intent.ShortcutIconResource) extra;
1001 final PackageManager packageManager = context.getPackageManager();
1002 Resources resources = packageManager.getResourcesForApplication(
1003 iconResource.packageName);
1004 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
1005 icon = resources.getDrawable(id);
1006 } catch (Exception e) {
1007 Log.w(LOG_TAG, "Could not load live folder icon: " + extra);
1008 }
1009 }
1010
1011 if (icon == null) {
1012 icon = context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1013 }
1014
1015 final LiveFolderInfo info = new LiveFolderInfo();
1016 info.icon = icon;
1017 info.filtered = filtered;
1018 info.title = name;
1019 info.iconResource = iconResource;
1020 info.uri = data.getData();
1021 info.baseIntent = baseIntent;
1022 info.displayMode = data.getIntExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
1023 LiveFolders.DISPLAY_MODE_GRID);
1024
1025 LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
1026 cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
1027 sModel.addFolder(info);
1028
1029 return info;
1030 }
1031
1032 private boolean findSingleSlot(CellLayout.CellInfo cellInfo) {
1033 final int[] xy = new int[2];
1034 if (findSlot(cellInfo, xy, 1, 1)) {
1035 cellInfo.cellX = xy[0];
1036 cellInfo.cellY = xy[1];
1037 return true;
1038 }
1039 return false;
1040 }
1041
1042 private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
1043 if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
1044 boolean[] occupied = mSavedState != null ?
1045 mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
1046 cellInfo = mWorkspace.findAllVacantCells(occupied);
1047 if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
1048 Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1049 return false;
1050 }
1051 }
1052 return true;
1053 }
1054
1055 private void showNotifications() {
1056 final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
1057 if (statusBar != null) {
1058 statusBar.expand();
1059 }
1060 }
1061
1062 private void startWallpaper() {
1063 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
1064 startActivity(Intent.createChooser(pickWallpaper, getString(R.string.chooser_wallpaper)));
1065 }
1066
1067 /**
1068 * Registers various intent receivers. The current implementation registers
1069 * only a wallpaper intent receiver to let other applications change the
1070 * wallpaper.
1071 */
1072 private void registerIntentReceivers() {
1073 if (sWallpaperReceiver == null) {
1074 final Application application = getApplication();
1075
1076 sWallpaperReceiver = new WallpaperIntentReceiver(application, this);
1077
1078 IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
1079 application.registerReceiver(sWallpaperReceiver, filter);
1080 } else {
1081 sWallpaperReceiver.setLauncher(this);
1082 }
1083
1084 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1085 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1086 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1087 filter.addDataScheme("package");
1088 registerReceiver(mApplicationsReceiver, filter);
1089 }
1090
1091 /**
1092 * Registers various content observers. The current implementation registers
1093 * only a favorites observer to keep track of the favorites applications.
1094 */
1095 private void registerContentObservers() {
1096 ContentResolver resolver = getContentResolver();
1097 resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver);
1098 }
1099
1100 @Override
1101 public boolean dispatchKeyEvent(KeyEvent event) {
1102 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1103 switch (event.getKeyCode()) {
1104 case KeyEvent.KEYCODE_BACK:
1105 mWorkspace.dispatchKeyEvent(event);
1106 closeFolder();
1107 closeDrawer();
1108 return true;
1109 case KeyEvent.KEYCODE_HOME:
1110 return true;
1111 }
1112 }
1113
1114 return super.dispatchKeyEvent(event);
1115 }
1116
1117 private void closeDrawer() {
1118 closeDrawer(true);
1119 }
1120
1121 private void closeDrawer(boolean animated) {
1122 if (mDrawer.isOpened()) {
1123 if (animated) {
1124 mDrawer.animateClose();
1125 } else {
1126 mDrawer.close();
1127 }
1128 if (mDrawer.hasFocus()) {
1129 mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1130 }
1131 }
1132 }
1133
1134 private void closeFolder() {
1135 Folder folder = mWorkspace.getOpenFolder();
1136 if (folder != null) {
1137 closeFolder(folder);
1138 }
1139 }
1140
1141 void closeFolder(Folder folder) {
1142 folder.getInfo().opened = false;
1143 ViewGroup parent = (ViewGroup) folder.getParent();
1144 if (parent != null) {
1145 parent.removeView(folder);
1146 }
1147 folder.onClose();
1148 }
1149
1150 /**
1151 * When the notification that favorites have changed is received, requests
1152 * a favorites list refresh.
1153 */
1154 private void onFavoritesChanged() {
1155 mDesktopLocked = true;
1156 mDrawer.lock();
1157 sModel.loadUserItems(false, this, false, false);
1158 }
1159
1160 void onDesktopItemsLoaded() {
1161 if (mDestroyed) return;
1162
1163 mAllAppsGrid.setAdapter(Launcher.getModel().getApplicationsAdapter());
1164 bindDesktopItems();
1165 }
1166
1167 /**
1168 * Refreshes the shortcuts shown on the workspace.
1169 */
1170 private void bindDesktopItems() {
1171 final ArrayList<ItemInfo> shortcuts = sModel.getDesktopItems();
1172 final ArrayList<LauncherGadgetInfo> gadgets = sModel.getDesktopGadgets();
1173 if (shortcuts == null || gadgets == null) {
1174 return;
1175 }
1176
1177 final Workspace workspace = mWorkspace;
1178 int count = workspace.getChildCount();
1179 for (int i = 0; i < count; i++) {
1180 ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
1181 }
1182
1183 if (DEBUG_USER_INTERFACE) {
1184 android.widget.Button finishButton = new android.widget.Button(this);
1185 finishButton.setText("Finish");
1186 workspace.addInScreen(finishButton, 1, 0, 0, 1, 1);
1187
1188 finishButton.setOnClickListener(new android.widget.Button.OnClickListener() {
1189 public void onClick(View v) {
1190 finish();
1191 }
1192 });
1193 }
1194
1195 final DesktopBinder binder = new DesktopBinder(this, shortcuts, gadgets);
1196 binder.startBindingItems();
1197 }
1198
1199 private void bindItems(Launcher.DesktopBinder binder,
1200 ArrayList<ItemInfo> shortcuts, int start, int count) {
1201
1202 final Workspace workspace = mWorkspace;
1203 final boolean desktopLocked = mDesktopLocked;
1204
1205 final int end = Math.min(start + DesktopBinder.ITEMS_COUNT, count);
1206 int i = start;
1207
1208 for ( ; i < end; i++) {
1209 final ItemInfo item = shortcuts.get(i);
1210 switch (item.itemType) {
1211 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1212 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1213 final View shortcut = createShortcut((ApplicationInfo) item);
1214 workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
1215 !desktopLocked);
1216 break;
1217 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1218 final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
1219 (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
1220 (UserFolderInfo) item);
1221 workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
1222 !desktopLocked);
1223 break;
1224 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
1225 final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
1226 R.layout.live_folder_icon, this,
1227 (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
1228 (LiveFolderInfo) item);
1229 workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
1230 !desktopLocked);
1231 break;
1232 case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
1233 final int screen = workspace.getCurrentScreen();
1234 final View view = mInflater.inflate(R.layout.widget_search,
1235 (ViewGroup) workspace.getChildAt(screen), false);
1236
1237 final Widget widget = (Widget) item;
1238 view.setTag(widget);
1239
1240 workspace.addWidget(view, widget, !desktopLocked);
1241 break;
1242 }
1243 }
1244
1245 workspace.requestLayout();
1246
1247 if (end >= count) {
1248 finishBindDesktopItems();
1249 binder.startBindingGadgets();
1250 } else {
1251 binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget();
1252 }
1253 }
1254
1255 private void finishBindDesktopItems() {
1256 if (mSavedState != null) {
1257 if (!mWorkspace.hasFocus()) {
1258 mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1259 }
1260
1261 final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
1262 if (userFolders != null) {
1263 for (long folderId : userFolders) {
1264 final FolderInfo info = sModel.findFolderById(folderId);
1265 if (info != null) {
1266 openFolder(info);
1267 }
1268 }
1269 final Folder openFolder = mWorkspace.getOpenFolder();
1270 if (openFolder != null) {
1271 openFolder.requestFocus();
1272 }
1273 }
1274
1275 final boolean allApps = mSavedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false);
1276 if (allApps) {
1277 mDrawer.open();
1278 }
1279
1280 mSavedState = null;
1281 }
1282
1283 if (mSavedInstanceState != null) {
1284 super.onRestoreInstanceState(mSavedInstanceState);
1285 mSavedInstanceState = null;
1286 }
1287
1288 if (mDrawer.isOpened() && !mDrawer.hasFocus()) {
1289 mDrawer.requestFocus();
1290 }
1291
1292 mDesktopLocked = false;
1293 mDrawer.unlock();
1294 }
1295
1296 private void bindGadgets(Launcher.DesktopBinder binder,
1297 ArrayList<LauncherGadgetInfo> gadgets, int start, int count) {
1298
1299 final Workspace workspace = mWorkspace;
1300 final boolean desktopLocked = mDesktopLocked;
1301
1302 final int end = Math.min(start + DesktopBinder.GADGETS_COUNT, count);
1303 int i = start;
1304
1305 for ( ; i < end; i++) {
1306 final LauncherGadgetInfo item = gadgets.get(i);
1307
1308 final int gadgetId = item.gadgetId;
1309 final GadgetProviderInfo gadgetInfo = mGadgetManager.getGadgetInfo(gadgetId);
1310 item.hostView = mGadgetHost.createView(this, gadgetId, gadgetInfo);
1311
1312 if (LOGD) Log.d(LOG_TAG, String.format("about to setGadget for id=%d, info=%s", gadgetId, gadgetInfo));
1313
1314 item.hostView.setGadget(gadgetId, gadgetInfo);
1315 item.hostView.setTag(item);
1316
1317 workspace.addInScreen(item.hostView, item.screen, item.cellX,
1318 item.cellY, item.spanX, item.spanY, !desktopLocked);
1319 }
1320
1321 workspace.requestLayout();
1322
1323 if (end >= count) {
1324 finishBindDesktopGadgets();
1325 } else {
1326 binder.obtainMessage(DesktopBinder.MESSAGE_BIND_GADGETS, i, count).sendToTarget();
1327 }
1328 }
1329
1330 private void finishBindDesktopGadgets() {
1331 }
1332
1333 DragController getDragController() {
1334 return mDragLayer;
1335 }
1336
1337 /**
1338 * Launches the intent referred by the clicked shortcut.
1339 *
1340 * @param v The view representing the clicked shortcut.
1341 */
1342 public void onClick(View v) {
1343 Object tag = v.getTag();
1344 if (tag instanceof ApplicationInfo) {
1345 // Open shortcut
1346 final Intent intent = ((ApplicationInfo) tag).intent;
1347 startActivitySafely(intent);
1348 } else if (tag instanceof FolderInfo) {
1349 handleFolderClick((FolderInfo) tag);
1350 }
1351 }
1352
1353 void startActivitySafely(Intent intent) {
1354 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1355 try {
1356 startActivity(intent);
1357 } catch (ActivityNotFoundException e) {
1358 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1359 } catch (SecurityException e) {
1360 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1361 Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
1362 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
1363 "or use the exported attribute for this activity.", e);
1364 }
1365 }
1366
1367 private void handleFolderClick(FolderInfo folderInfo) {
1368 if (!folderInfo.opened) {
1369 // Close any open folder
1370 closeFolder();
1371 // Open the requested folder
1372 openFolder(folderInfo);
1373 } else {
1374 // Find the open folder...
1375 Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
1376 int folderScreen;
1377 if (openFolder != null) {
1378 folderScreen = mWorkspace.getScreenForView(openFolder);
1379 // .. and close it
1380 closeFolder(openFolder);
1381 if (folderScreen != mWorkspace.getCurrentScreen()) {
1382 // Close any folder open on the current screen
1383 closeFolder();
1384 // Pull the folder onto this screen
1385 openFolder(folderInfo);
1386 }
1387 }
1388 }
1389 }
1390
1391 private void loadWallpaper() {
1392 // The first time the application is started, we load the wallpaper from
1393 // the ApplicationContext
1394 if (sWallpaper == null) {
1395 final Drawable drawable = getWallpaper();
1396 if (drawable instanceof BitmapDrawable) {
1397 sWallpaper = ((BitmapDrawable) drawable).getBitmap();
1398 } else {
1399 throw new IllegalStateException("The wallpaper must be a BitmapDrawable.");
1400 }
1401 }
1402 mWorkspace.loadWallpaper(sWallpaper);
1403 }
1404
1405 /**
1406 * Opens the user fodler described by the specified tag. The opening of the folder
1407 * is animated relative to the specified View. If the View is null, no animation
1408 * is played.
1409 *
1410 * @param folderInfo The FolderInfo describing the folder to open.
1411 */
1412 private void openFolder(FolderInfo folderInfo) {
1413 Folder openFolder;
1414
1415 if (folderInfo instanceof UserFolderInfo) {
1416 openFolder = UserFolder.fromXml(this);
1417 } else if (folderInfo instanceof LiveFolderInfo) {
1418 openFolder = com.android.launcher.LiveFolder.fromXml(this, folderInfo);
1419 } else {
1420 return;
1421 }
1422
1423 openFolder.setDragger(mDragLayer);
1424 openFolder.setLauncher(this);
1425
1426 openFolder.bind(folderInfo);
1427 folderInfo.opened = true;
1428
1429 mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4);
1430 openFolder.onOpen();
1431 }
1432
1433 /**
1434 * Returns true if the workspace is being loaded. When the workspace is loading,
1435 * no user interaction should be allowed to avoid any conflict.
1436 *
1437 * @return True if the workspace is locked, false otherwise.
1438 */
1439 boolean isWorkspaceLocked() {
1440 return mDesktopLocked;
1441 }
1442
1443 public boolean onLongClick(View v) {
1444 if (mDesktopLocked) {
1445 return false;
1446 }
1447
1448 if (!(v instanceof CellLayout)) {
1449 v = (View) v.getParent();
1450 }
1451
1452 CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
1453
1454 // This happens when long clicking an item with the dpad/trackball
1455 if (cellInfo == null) {
1456 return true;
1457 }
1458
1459 if (mWorkspace.allowLongPress()) {
1460 if (cellInfo.cell == null) {
1461 if (cellInfo.valid) {
1462 // User long pressed on empty space
1463 showAddDialog(cellInfo);
1464 }
1465 } else {
1466 if (!(cellInfo.cell instanceof Folder)) {
1467 // User long pressed on an item
1468 mWorkspace.startDrag(cellInfo);
1469 }
1470 }
1471 }
1472 return true;
1473 }
1474
1475 static LauncherModel getModel() {
1476 return sModel;
1477 }
1478
1479 void closeAllApplications() {
1480 mDrawer.close();
1481 }
1482
1483 boolean isDrawerDown() {
1484 return !mDrawer.isMoving() && !mDrawer.isOpened();
1485 }
1486
1487 boolean isDrawerUp() {
1488 return mDrawer.isOpened() && !mDrawer.isMoving();
1489 }
1490
1491 Workspace getWorkspace() {
1492 return mWorkspace;
1493 }
1494
1495 GridView getApplicationsGrid() {
1496 return mAllAppsGrid;
1497 }
1498
1499 @Override
1500 protected Dialog onCreateDialog(int id) {
1501 switch (id) {
1502 case DIALOG_CREATE_SHORTCUT:
1503 return new CreateShortcut().createDialog();
1504 case DIALOG_RENAME_FOLDER:
1505 return new RenameFolder().createDialog();
1506 }
1507
1508 return super.onCreateDialog(id);
1509 }
1510
1511 @Override
1512 protected void onPrepareDialog(int id, Dialog dialog) {
1513 switch (id) {
1514 case DIALOG_CREATE_SHORTCUT:
1515 mWorkspace.lock();
1516 break;
1517 case DIALOG_RENAME_FOLDER:
1518 mWorkspace.lock();
1519 EditText input = (EditText) dialog.findViewById(R.id.folder_name);
1520 final CharSequence text = mFolderInfo.title;
1521 input.setText(text);
1522 input.setSelection(0, text.length());
1523 break;
1524 }
1525 }
1526
1527 void showRenameDialog(FolderInfo info) {
1528 mFolderInfo = info;
1529 mWaitingForResult = true;
1530 showDialog(DIALOG_RENAME_FOLDER);
1531 }
1532
1533 private void showAddDialog(CellLayout.CellInfo cellInfo) {
1534 mAddItemCellInfo = cellInfo;
1535 mWaitingForResult = true;
1536 showDialog(DIALOG_CREATE_SHORTCUT);
1537 }
1538
1539 private class RenameFolder {
1540 private EditText mInput;
1541
1542 Dialog createDialog() {
1543 mWaitingForResult = true;
1544 final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
1545 mInput = (EditText) layout.findViewById(R.id.folder_name);
1546
1547 AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1548 builder.setIcon(0);
1549 builder.setTitle(getString(R.string.rename_folder_title));
1550 builder.setCancelable(true);
1551 builder.setOnCancelListener(new Dialog.OnCancelListener() {
1552 public void onCancel(DialogInterface dialog) {
1553 cleanup();
1554 }
1555 });
1556 builder.setNegativeButton(getString(R.string.cancel_action),
1557 new Dialog.OnClickListener() {
1558 public void onClick(DialogInterface dialog, int which) {
1559 cleanup();
1560 }
1561 }
1562 );
1563 builder.setPositiveButton(getString(R.string.rename_action),
1564 new Dialog.OnClickListener() {
1565 public void onClick(DialogInterface dialog, int which) {
1566 changeFolderName();
1567 }
1568 }
1569 );
1570 builder.setView(layout);
1571 return builder.create();
1572 }
1573
1574 private void changeFolderName() {
1575 final String name = mInput.getText().toString();
1576 if (!TextUtils.isEmpty(name)) {
1577 // Make sure we have the right folder info
1578 mFolderInfo = sModel.findFolderById(mFolderInfo.id);
1579 mFolderInfo.title = name;
1580 LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
1581
1582 if (mDesktopLocked) {
1583 mDrawer.lock();
1584 sModel.loadUserItems(false, Launcher.this, false, false);
1585 } else {
1586 final FolderIcon folderIcon = (FolderIcon)
1587 mWorkspace.getViewForTag(mFolderInfo);
1588 if (folderIcon != null) {
1589 folderIcon.setText(name);
1590 getWorkspace().requestLayout();
1591 } else {
1592 mDesktopLocked = true;
1593 mDrawer.lock();
1594 sModel.loadUserItems(false, Launcher.this, false, false);
1595 }
1596 }
1597 }
1598 cleanup();
1599 }
1600
1601 private void cleanup() {
1602 mWorkspace.unlock();
1603 dismissDialog(DIALOG_RENAME_FOLDER);
1604 mWaitingForResult = false;
1605 mFolderInfo = null;
1606 }
1607 }
1608
1609 /**
1610 * Displays the shortcut creation dialog and launches, if necessary, the
1611 * appropriate activity.
1612 */
1613 private class CreateShortcut implements AdapterView.OnItemClickListener,
1614 DialogInterface.OnCancelListener {
1615 private AddAdapter mAdapter;
1616 private ListView mList;
1617
1618 Dialog createDialog() {
1619 mWaitingForResult = true;
1620
1621 mAdapter = new AddAdapter(Launcher.this);
1622
1623 final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1624 builder.setTitle(getString(R.string.menu_item_add_item));
1625 builder.setIcon(0);
1626
1627 mList = (ListView)
1628 View.inflate(Launcher.this, R.layout.create_shortcut_list, null);
1629 mList.setAdapter(mAdapter);
1630 mList.setOnItemClickListener(this);
1631 builder.setView(mList);
1632 builder.setInverseBackgroundForced(true);
1633
1634 AlertDialog dialog = builder.create();
1635 dialog.setOnCancelListener(this);
1636
1637 WindowManager.LayoutParams attributes = dialog.getWindow().getAttributes();
1638 attributes.gravity = Gravity.TOP;
1639 dialog.onWindowAttributesChanged(attributes);
1640
1641 return dialog;
1642 }
1643
1644 public void onCancel(DialogInterface dialog) {
1645 mWaitingForResult = false;
1646 cleanup();
1647 }
1648
1649 private void cleanup() {
1650 mWorkspace.unlock();
1651 dismissDialog(DIALOG_CREATE_SHORTCUT);
1652 }
1653
1654 public void onItemClick(AdapterView parent, View view, int position, long id) {
1655 // handle which item was clicked based on position
1656 // this will launch off pick intent
1657
1658 Object tag = view.getTag();
1659 if (tag instanceof AddAdapter.ListItem) {
1660 AddAdapter.ListItem item = (AddAdapter.ListItem) tag;
1661 cleanup();
1662 switch (item.actionTag) {
1663 case AddAdapter.ITEM_APPLICATION: {
1664 Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1665 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1666
1667 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1668 pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
1669 startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION);
1670 break;
1671 }
1672
1673 case AddAdapter.ITEM_SHORTCUT: {
1674 Intent shortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
1675
1676 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1677 pickIntent.putExtra(Intent.EXTRA_INTENT, shortcutIntent);
1678 pickIntent.putExtra(Intent.EXTRA_TITLE,
1679 getText(R.string.title_select_shortcut));
1680 startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
1681 break;
1682 }
1683
1684 case AddAdapter.ITEM_SEARCH: {
1685 addSearch();
1686 break;
1687 }
1688
1689 case AddAdapter.ITEM_GADGET: {
1690 int gadgetId = Launcher.this.mGadgetHost.allocateGadgetId();
1691
1692 Intent pickIntent = new Intent(GadgetManager.ACTION_GADGET_PICK);
1693 pickIntent.putExtra(GadgetManager.EXTRA_GADGET_ID, gadgetId);
1694 startActivityForResult(pickIntent, REQUEST_PICK_GADGET);
1695 break;
1696 }
1697
1698 case AddAdapter.ITEM_LIVE_FOLDER: {
1699 Intent liveFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
1700
1701 Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1702 pickIntent.putExtra(Intent.EXTRA_INTENT, liveFolderIntent);
1703 pickIntent.putExtra(Intent.EXTRA_TITLE,
1704 getText(R.string.title_select_live_folder));
1705 startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
1706 break;
1707 }
1708
1709 case AddAdapter.ITEM_FOLDER: {
1710 addFolder(!mDesktopLocked);
1711 dismissDialog(DIALOG_CREATE_SHORTCUT);
1712 break;
1713 }
1714
1715 case AddAdapter.ITEM_WALLPAPER: {
1716 startWallpaper();
1717 break;
1718 }
1719
1720 }
1721
1722 }
1723 }
1724 }
1725
1726 /**
1727 * Receives notifications when applications are added/removed.
1728 */
1729 private class ApplicationsIntentReceiver extends BroadcastReceiver {
1730 @Override
1731 public void onReceive(Context context, Intent intent) {
1732 boolean reloadWorkspace = false;
1733 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
1734 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
1735 removeShortcutsForPackage(intent.getData().getSchemeSpecificPart());
1736 } else {
1737 reloadWorkspace = true;
1738 }
1739 }
1740 removeDialog(DIALOG_CREATE_SHORTCUT);
The Android Open Source Projectbc219c32009-03-09 11:52:14 -07001741 sModel.dropApplicationCache();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001742 if (!reloadWorkspace) {
1743 sModel.loadApplications(false, Launcher.this, false);
1744 } else {
1745 sModel.loadUserItems(false, Launcher.this, false, true);
1746 }
1747 }
1748 }
1749
1750 /**
1751 * Receives notifications whenever the user favorites have changed.
1752 */
1753 private class FavoritesChangeObserver extends ContentObserver {
1754 public FavoritesChangeObserver() {
1755 super(new Handler());
1756 }
1757
1758 @Override
1759 public void onChange(boolean selfChange) {
1760 onFavoritesChanged();
1761 }
1762 }
1763
1764 /**
1765 * Receives intents from other applications to change the wallpaper.
1766 */
1767 private static class WallpaperIntentReceiver extends BroadcastReceiver {
1768 private final Application mApplication;
1769 private WeakReference<Launcher> mLauncher;
1770
1771 WallpaperIntentReceiver(Application application, Launcher launcher) {
1772 mApplication = application;
1773 setLauncher(launcher);
1774 }
1775
1776 void setLauncher(Launcher launcher) {
1777 mLauncher = new WeakReference<Launcher>(launcher);
1778 }
1779
1780 @Override
1781 public void onReceive(Context context, Intent intent) {
1782 // Load the wallpaper from the ApplicationContext and store it locally
1783 // until the Launcher Activity is ready to use it
1784 final Drawable drawable = mApplication.getWallpaper();
1785 if (drawable instanceof BitmapDrawable) {
1786 sWallpaper = ((BitmapDrawable) drawable).getBitmap();
1787 } else {
1788 throw new IllegalStateException("The wallpaper must be a BitmapDrawable.");
1789 }
1790
1791 // If Launcher is alive, notify we have a new wallpaper
1792 if (mLauncher != null) {
1793 final Launcher launcher = mLauncher.get();
1794 if (launcher != null) {
1795 launcher.loadWallpaper();
1796 }
1797 }
1798 }
1799 }
1800
1801 private class DrawerManager implements SlidingDrawer.OnDrawerOpenListener,
1802 SlidingDrawer.OnDrawerCloseListener, SlidingDrawer.OnDrawerScrollListener {
1803 private boolean mOpen;
1804
1805 public void onDrawerOpened() {
1806 if (!mOpen) {
1807 mHandleIcon.reverseTransition(150);
1808 final Rect bounds = mWorkspace.mDrawerBounds;
1809
1810 View view = mAllAppsGrid;
1811 view.getDrawingRect(bounds);
1812
1813 while (view != mDragLayer) {
1814 bounds.offset(view.getLeft(), view.getTop());
1815 view = (View) view.getParent();
1816 }
1817
1818 mOpen = true;
1819 }
1820 }
1821
1822 public void onDrawerClosed() {
1823 if (mOpen) {
1824 mHandleIcon.reverseTransition(150);
1825 mWorkspace.mDrawerBounds.setEmpty();
1826 mOpen = false;
1827 }
1828 mAllAppsGrid.setSelection(0);
1829 mAllAppsGrid.clearTextFilter();
1830 }
1831
1832 public void onScrollStarted() {
1833 }
1834
1835 public void onScrollEnded() {
1836 }
1837 }
1838
1839 private static class DesktopBinder extends Handler {
1840 static final int MESSAGE_BIND_ITEMS = 0x1;
1841 static final int MESSAGE_BIND_GADGETS = 0x2;
1842 // Number of items to bind in every pass
1843 static final int ITEMS_COUNT = 6;
1844 static final int GADGETS_COUNT = 1;
1845
1846 private final ArrayList<ItemInfo> mShortcuts;
1847 private final ArrayList<LauncherGadgetInfo> mGadgets;
1848 private final WeakReference<Launcher> mLauncher;
1849
1850 DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts,
1851 ArrayList<LauncherGadgetInfo> gadgets) {
1852
1853 mLauncher = new WeakReference<Launcher>(launcher);
1854 mShortcuts = shortcuts;
1855 mGadgets = gadgets;
1856 }
1857
1858 public void startBindingItems() {
1859 obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget();
1860 }
1861
1862 public void startBindingGadgets() {
1863 obtainMessage(MESSAGE_BIND_GADGETS, 0, mGadgets.size()).sendToTarget();
1864 }
1865
1866 @Override
1867 public void handleMessage(Message msg) {
1868 Launcher launcher = mLauncher.get();
1869 if (launcher == null) {
1870 return;
1871 }
1872
1873 switch (msg.what) {
1874 case MESSAGE_BIND_ITEMS: {
1875 launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2);
1876 break;
1877 }
1878 case MESSAGE_BIND_GADGETS: {
1879 launcher.bindGadgets(this, mGadgets, msg.arg1, msg.arg2);
1880 break;
1881 }
1882 }
1883 }
1884 }
1885}