blob: 2ae50377c439ea13a4789e38f4d027822c3e1ba6 [file] [log] [blame]
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001/*
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.ContentResolver;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.database.ContentObserver;
36import android.graphics.Bitmap;
37import android.graphics.drawable.BitmapDrawable;
38import android.graphics.drawable.Drawable;
39import android.graphics.drawable.TransitionDrawable;
40import android.hardware.SensorListener;
41import android.hardware.SensorManager;
42import android.net.Uri;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Parcelable;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.SystemClock;
50import android.os.SystemProperties;
51import android.provider.Contacts;
52import android.telephony.PhoneNumberUtils;
53import android.text.Selection;
54import android.text.SpannableStringBuilder;
55import android.text.TextUtils;
56import android.text.method.TextKeyListener;
57import android.util.Config;
58import android.util.Log;
59import android.view.Display;
60import android.view.Gravity;
61import android.view.KeyEvent;
62import android.view.LayoutInflater;
63import android.view.Menu;
64import android.view.MenuItem;
65import android.view.View;
66import android.view.ViewGroup;
67import android.view.WindowManager;
68import android.view.View.OnLongClickListener;
69import android.widget.EditText;
70import android.widget.ExpandableListView;
71import android.widget.ImageView;
72import android.widget.TextView;
73import android.widget.Toast;
74import android.app.IWallpaperService;
75
76import com.android.internal.provider.Settings;
77import com.android.internal.widget.SlidingDrawer;
78
79import java.lang.ref.WeakReference;
80import java.util.ArrayList;
81
82/**
83 * Default launcher application.
84 */
85public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener {
86 static final String LOG_TAG = "Launcher";
87
88 private static final boolean PROFILE_STARTUP = false;
89 private static final boolean DEBUG_USER_INTERFACE = false;
90
91 private static final String USE_OPENGL_BY_DEFAULT = "false";
92
93 private static final boolean REMOVE_SHORTCUT_ON_PACKAGE_REMOVE = false;
94
95 // Type: boolean
96 private static final String PROPERTY_USE_OPENGL = "launcher.opengl";
97 // Type: boolean
98 private static final String PROPERTY_USE_SENSORS = "launcher.sensors";
99
100 private static final boolean USE_OPENGL = true;
101 private static final boolean USE_SENSORS = false;
102
103 private static final int WALLPAPER_SCREENS_SPAN = 2;
104
105 private static final int MENU_GROUP_ADD = 1;
106 private static final int MENU_ADD = Menu.FIRST + 1;
107 private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
108 private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
109 private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
110 private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
111
112 private static final int REQUEST_CREATE_SHORTCUT = 1;
113 private static final int REQUEST_CHOOSE_PHOTO = 2;
114 private static final int REQUEST_UPDATE_PHOTO = 3;
115
116 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
117
118 static final int DEFAULT_SCREN = 1;
119 static final int NUMBER_CELLS_X = 4;
120 static final int NUMBER_CELLS_Y = 4;
121
122 private static final int DIALOG_CREATE_SHORTCUT = 1;
123 static final int DIALOG_RENAME_FOLDER = 2;
124
125 // Type: int
126 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
127 // Type: boolean
128 private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder";
129 // Type: long
130 private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
131 // Type: int
132 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
133 // Type: int
134 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX";
135 // Type: int
136 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
137 // Type: int
138 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX";
139 // Type: int
140 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY";
141 // Type: int
142 private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX";
143 // Type: int
144 private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY";
145 // Type: int[]
146 private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells";
147 // Type: boolean
148 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
149 // Type: long
150 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
151
152 private static LauncherModel sModel;
153
154 private static Bitmap sWallpaper;
155
156 // Indicates whether the OpenGL pipeline was enabled, either through
157 // USE_OPENGL_BY_DEFAULT or the system property launcher.opengl
158 static boolean sOpenGlEnabled;
159
160 private static final Object sLock = new Object();
161 private static int sScreen = DEFAULT_SCREN;
162
163 private static WallpaperIntentReceiver sWallpaperReceiver;
164
165 private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
166 private final ContentObserver mObserver = new FavoritesChangeObserver();
167
168 private final Handler mHandler = new Handler();
169 private LayoutInflater mInflater;
170
171 private SensorManager mSensorManager;
172 private SensorHandler mSensorHandler;
173
174 private DragLayer mDragLayer;
175 private Workspace mWorkspace;
176
177 private CellLayout.CellInfo mAddItemCellInfo;
178 private CellLayout.CellInfo mMenuAddInfo;
179 private final int[] mCellCoordinates = new int[2];
180 private UserFolderInfo mFolderInfo;
181
182 private SlidingDrawer mDrawer;
183 private TransitionDrawable mHandleIcon;
184 private AllAppsGridView mAllAppsGrid;
185
186 private boolean mDesktopLocked = true;
187 private Bundle mSavedState;
188
189 private SpannableStringBuilder mDefaultKeySsb = null;
190
191 private boolean mDestroyed;
192
193 private boolean mRestoring;
194 private boolean mWaitingForResult;
195
196 @Override
197 protected void onCreate(Bundle savedInstanceState) {
198 dalvik.system.VMRuntime.getRuntime().setMinimumHeapSize(4 * 1024 * 1024);
199
200 super.onCreate(savedInstanceState);
201 mInflater = getLayoutInflater();
202
203 if (PROFILE_STARTUP) {
204 android.os.Debug.startMethodTracing("/sdcard/launcher");
205 }
206
207 setWallpaperDimension();
208
209 enableSensors();
210 enableOpenGL();
211
212 if (sModel == null) {
213 sModel = new LauncherModel();
214 }
215
216 setContentView(R.layout.launcher);
217 setupViews();
218
219 registerIntentReceivers();
220 registerContentObservers();
221
222 mSavedState = savedInstanceState;
223 restoreState(mSavedState);
224
225 if (PROFILE_STARTUP) {
226 android.os.Debug.stopMethodTracing();
227 }
228
229 if (!mRestoring) {
230 startLoaders();
231 }
232
233 // For handling default keys
234 mDefaultKeySsb = new SpannableStringBuilder();
235 Selection.setSelection(mDefaultKeySsb, 0);
236 }
237
238 static int getScreen() {
239 synchronized (sLock) {
240 return sScreen;
241 }
242 }
243
244 static void setScreen(int screen) {
245 synchronized (sLock) {
246 sScreen = screen;
247 }
248 }
249
250 private void startLoaders() {
251 sModel.loadApplications(true, this);
252 sModel.loadUserItems(true, this);
253 mRestoring = false;
254 }
255
256 @Override
257 public void onConfigurationChanged(Configuration newConfig) {
258 super.onConfigurationChanged(newConfig);
259
260 // When MMC/MNC changes, so can applications, so we reload them
261 sModel.loadApplications(false, Launcher.this);
262 }
263
264 private void setWallpaperDimension() {
265 IBinder binder = ServiceManager.getService(WALLPAPER_SERVICE);
266 IWallpaperService wallpaperService = IWallpaperService.Stub.asInterface(binder);
267
268 Display display = getWindowManager().getDefaultDisplay();
269 boolean isPortrait = display.getWidth() < display.getHeight();
270
271 final int width = isPortrait ? display.getWidth() : display.getHeight();
272 final int height = isPortrait ? display.getHeight() : display.getWidth();
273 try {
274 wallpaperService.setDimensionHints(width * WALLPAPER_SCREENS_SPAN, height);
275 } catch (RemoteException e) {
276 // System is dead!
277 }
278 }
279
280 private void enableSensors() {
281 //noinspection PointlessBooleanExpression,ConstantConditions
282 if (USE_SENSORS || "true".equals(SystemProperties.get(PROPERTY_USE_SENSORS, "false"))) {
283 if (Config.LOGD) {
284 Log.d(LOG_TAG, "Launcher activating sensors");
285 }
286 mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
287 mSensorHandler = new SensorHandler();
288 }
289 }
290
291 private void enableOpenGL() {
292 //noinspection PointlessBooleanExpression,ConstantConditions
293 if (USE_OPENGL && "true".equals(SystemProperties.get(PROPERTY_USE_OPENGL,
294 USE_OPENGL_BY_DEFAULT))) {
295 if (Config.LOGD) {
296 Log.d(LOG_TAG, "Launcher starting in OpenGL");
297 }
298 //requestWindowFeature(Window.FEATURE_OPENGL);
299 //sOpenGlEnabled = true;
300 } else {
301 sOpenGlEnabled = false;
302 }
303 }
304
305 @Override
306 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
307 if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
308 switch (requestCode) {
309 case REQUEST_CREATE_SHORTCUT:
310 completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked);
311 break;
312 case REQUEST_CHOOSE_PHOTO:
313 completeAddPhotoFrame(data, mAddItemCellInfo);
314 break;
315 case REQUEST_UPDATE_PHOTO:
316 completeUpdatePhotoFrame(data, mAddItemCellInfo);
317 break;
318 }
319 }
320 mWaitingForResult = false;
321 }
322
323 @Override
324 protected void onResume() {
325 super.onResume();
326
327 if (mRestoring) {
328 startLoaders();
329 }
330
331 if (mSensorManager != null) {
332 mSensorManager.registerListener(mSensorHandler, SensorManager.SENSOR_ACCELEROMETER);
333 }
334 }
335
336 @Override
337 protected void onStop() {
338 if (mSensorManager != null) {
339 mSensorManager.unregisterListener(mSensorHandler);
340 }
341
342 super.onStop();
343 }
344
345 @Override
346 public boolean onKeyUp(int keyCode, KeyEvent event) {
347 boolean handled = super.onKeyUp(keyCode, event);
348 if (keyCode == KeyEvent.KEYCODE_SEARCH) {
349 handled = mWorkspace.snapToSearch();
350 }
351 return handled;
352 }
353
354 @Override
355 public boolean onKeyDown(int keyCode, KeyEvent event) {
356 boolean handled = super.onKeyDown(keyCode, event);
357 if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) {
358 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
359 keyCode, event);
360 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
361 // something usable has been typed - dispatch it now.
362 final String str = mDefaultKeySsb.toString();
363
364 boolean isDialable = true;
365 final int count = str.length();
366 for (int i = 0; i < count; i++) {
367 if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
368 isDialable = false;
369 break;
370 }
371 }
372 Intent intent;
373 if (isDialable) {
374 intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
375 } else {
376 intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
377 intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
378 }
379
380 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
381
382 try {
383 startActivity(intent);
384 } catch (android.content.ActivityNotFoundException ex) {
385 // Oh well... no one knows how to filter/dial. Life goes on.
386 }
387
388 mDefaultKeySsb.clear();
389 mDefaultKeySsb.clearSpans();
390 Selection.setSelection(mDefaultKeySsb, 0);
391
392 return true;
393 }
394 }
395
396 return handled;
397 }
398
399 /**
400 * Restores the previous state, if it exists.
401 *
402 * @param savedState The previous state.
403 */
404 private void restoreState(Bundle savedState) {
405 if (savedState == null) {
406 return;
407 }
408
409 final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
410 if (currentScreen > -1) {
411 mWorkspace.setCurrentScreen(currentScreen);
412 }
413
414 final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
415 if (addScreen > -1) {
416 mAddItemCellInfo = new CellLayout.CellInfo();
417 final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
418 addItemCellInfo.valid = true;
419 addItemCellInfo.screen = addScreen;
420 addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
421 addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
422 addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
423 addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
424 addItemCellInfo.findVacantCellsFromOccupied(
425 savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS),
426 savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X),
427 savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y));
428 mRestoring = true;
429 }
430
431
432 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
433 if (renameFolder) {
434 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
435 mFolderInfo = sModel.getFolderById(this, id);
436 mRestoring = true;
437 }
438 }
439
440 /**
441 * Finds all the views we need and configure them properly.
442 */
443 private void setupViews() {
444 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
445 final DragLayer dragLayer = mDragLayer;
446
447 mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
448 final Workspace workspace = mWorkspace;
449
450 mDrawer = (SlidingDrawer) dragLayer.findViewById(R.id.drawer);
451 final SlidingDrawer drawer = mDrawer;
452
453 mAllAppsGrid = (AllAppsGridView) drawer.getContent();
454 final AllAppsGridView grid = mAllAppsGrid;
455
456 final DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
457
458 final ImageView handleIcon = (ImageView) drawer.findViewById(R.id.all_apps);
459 mHandleIcon = (TransitionDrawable) handleIcon.getDrawable();
460 mHandleIcon.setCrossFadeEnabled(true);
461
462 drawer.lock();
463 final DrawerManager drawerManager = new DrawerManager();
464 drawer.setOnDrawerOpenListener(drawerManager);
465 drawer.setOnDrawerCloseListener(drawerManager);
466 drawer.setOnDrawerScrollListener(drawerManager);
467
468 grid.setTextFilterEnabled(true);
469 grid.setDragger(dragLayer);
470 grid.setLauncher(this);
471 if (sOpenGlEnabled) {
472 grid.setScrollingCacheEnabled(false);
473 grid.setFadingEdgeLength(0);
474 }
475
476 workspace.setOnLongClickListener(this);
477 workspace.setDragger(dragLayer);
478 workspace.setLauncher(this);
479 loadWallpaper();
480
481 deleteZone.setLauncher(this);
482 deleteZone.setDragController(dragLayer);
483 deleteZone.setHandle(handleIcon);
484
485 dragLayer.setIgnoredDropTarget(grid);
486 dragLayer.setDragScoller(workspace);
487 dragLayer.setDragListener(deleteZone);
488
489 if (DEBUG_USER_INTERFACE) {
490 android.widget.Button finishButton = new android.widget.Button(this);
491 finishButton.setText("Finish");
492 workspace.addInScreen(finishButton, 1, 0, 0, 1, 1);
493
494 finishButton.setOnClickListener(new android.widget.Button.OnClickListener() {
495 public void onClick(View v) {
496 finish();
497 }
498 });
499 }
500 }
501
502 /**
503 * Creates a view representing a shortcut.
504 *
505 * @param info The data structure describing the shortcut.
506 *
507 * @return A View inflated from R.layout.application.
508 */
509 View createShortcut(ApplicationInfo info) {
510 return createShortcut(R.layout.application,
511 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
512 }
513
514 /**
515 * Creates a view representing a shortcut inflated from the specified resource.
516 *
517 * @param layoutResId The id of the XML layout used to create the shortcut.
518 * @param parent The group the shortcut belongs to.
519 * @param info The data structure describing the shortcut.
520 *
521 * @return A View inflated from layoutResId.
522 */
523 View createShortcut(int layoutResId, ViewGroup parent, ApplicationInfo info) {
524 TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
525
526 if (!info.filtered) {
527 info.icon = Utilities.createIconThumbnail(info.icon, this);
528 info.filtered = true;
529 }
530
531 favorite.setCompoundDrawablesWithIntrinsicBounds(null, info.icon, null, null);
532 favorite.setText(info.title);
533 favorite.setTag(info);
534 favorite.setOnClickListener(this);
535
536 return favorite;
537 }
538
539 void addApplicationShortcut(ApplicationInfo info) {
540 mAddItemCellInfo.screen = mWorkspace.getCurrentScreen();
541 mWorkspace.addApplicationShortcut(info, mAddItemCellInfo);
542 }
543
544 /**
545 * Add a shortcut to the workspace.
546 *
547 * @param data The intent describing the shortcut.
548 * @param cellInfo The position on screen where to create the shortcut.
549 * @param insertAtFirst
550 */
551 private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo,
552 boolean insertAtFirst) {
553
554 cellInfo.screen = mWorkspace.getCurrentScreen();
555 final ApplicationInfo info = addShortcut(this, data, cellInfo, false);
556
557 if (!mRestoring) {
558 sModel.addDesktopItem(info);
559
560 final View view = createShortcut(info);
561 mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
562 } else if (sModel.isDesktopLoaded()) {
563 sModel.addDesktopItem(info);
564 }
565 }
566
567 static ApplicationInfo addShortcut(Context context, Intent data,
568 CellLayout.CellInfo cellInfo, boolean notify) {
569
570 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
571 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
572 Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
573
574 Drawable icon = null;
575 boolean filtered = false;
576 boolean customIcon = false;
577 Intent.ShortcutIconResource iconResource = null;
578
579 if (bitmap != null) {
580 icon = new BitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
581 filtered = true;
582 customIcon = true;
583 } else {
584 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
585 if (extra != null && extra instanceof Intent.ShortcutIconResource) {
586 try {
587 iconResource = (Intent.ShortcutIconResource) extra;
588 final PackageManager packageManager = context.getPackageManager();
589 Resources resources = packageManager.getResourcesForApplication(
590 iconResource.packageName);
591 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
592 icon = resources.getDrawable(id);
593 } catch (Exception e) {
594 Log.w(LOG_TAG, "Could not load shortcut icon: " + extra);
595 }
596 }
597 }
598
599 if (icon == null) {
600 icon = context.getPackageManager().getDefaultActivityIcon();
601 }
602
603 final ApplicationInfo info = new ApplicationInfo();
604 info.icon = icon;
605 info.filtered = filtered;
606 info.title = name;
607 info.intent = intent;
608 info.customIcon = customIcon;
609 info.iconResource = iconResource;
610
611 LauncherModel.addItemToDatabase(context, info, Settings.Favorites.CONTAINER_DESKTOP,
612 cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
613 return info;
614 }
615
616 /**
617 * Add a PhotFrame to the workspace.
618 *
619 * @param data The intent describing the photo.
620 * @param cellInfo The position on screen where to create the shortcut.
621 */
622 private void completeAddPhotoFrame(Intent data, CellLayout.CellInfo cellInfo) {
623 final Bundle extras = data.getExtras();
624 if (extras != null) {
625 Bitmap photo = extras.getParcelable("data");
626
627 Widget info = Widget.makePhotoFrame();
628 info.photo = photo;
629
630 final int[] xy = mCellCoordinates;
631 if (!findSlot(cellInfo, xy, info.spanX, info.spanY)) return;
632
633 LauncherModel.addItemToDatabase(this, info, Settings.Favorites.CONTAINER_DESKTOP,
634 mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
635
636 if (!mRestoring) {
637 sModel.addDesktopItem(info);
638
639 final PhotoFrame view = (PhotoFrame) mInflater.inflate(info.layoutResource, null);
640 view.setImageBitmap(photo);
641 view.setTag(info);
642
643 mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, info.spanY);
644 } else if (sModel.isDesktopLoaded()) {
645 sModel.addDesktopItem(info);
646 }
647 }
648 }
649
650 /**
651 * Updates a workspace PhotoFrame.
652 *
653 * @param data The intent describing the photo.
654 * @param cellInfo The position on screen of the PhotoFrame to update.
655 */
656 private void completeUpdatePhotoFrame(Intent data, CellLayout.CellInfo cellInfo) {
657 final Bundle extras = data.getExtras();
658 if (extras != null) {
659 Widget info;
660 Bitmap photo = extras.getParcelable("data");
661
662 if (!mRestoring) {
663 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
664 final PhotoFrame view = (PhotoFrame) layout.findCell(cellInfo.cellX, cellInfo.cellY,
665 cellInfo.spanX, cellInfo.spanY, null);
666 view.setImageBitmap(photo);
667 info = (Widget) view.getTag();
668 } else {
669 info = LauncherModel.getPhotoFrameInfo(this, cellInfo.screen,
670 cellInfo.cellX, cellInfo.cellY);
671 }
672
673 info.photo = photo;
674 LauncherModel.updateItemInDatabase(this, info);
675 }
676 }
677
678 /**
679 * Starts a new Intent to let the user update the PhotoFrame defined by the
680 * specified Widget.
681 *
682 * @param widget The Widget info defining which PhotoFrame to update.
683 */
684 void updatePhotoFrame(Widget widget) {
685 CellLayout.CellInfo info = new CellLayout.CellInfo();
686 info.screen = widget.screen;
687 info.cellX = widget.cellX;
688 info.cellY = widget.cellY;
689 info.spanX = widget.spanX;
690 info.spanY = widget.spanY;
691 mAddItemCellInfo = info;
692
693 startActivityForResult(createPhotoPickIntent(), Launcher.REQUEST_UPDATE_PHOTO);
694 }
695
696 /**
697 * Creates an Intent used to let the user pick a photo for a PhotoFrame.
698 *
699 * @return The Intent to pick a photo suited for a PhotoFrame.
700 */
701 private static Intent createPhotoPickIntent() {
702 // TODO: Move this method to PhotoFrame?
703 // TODO: get these values from constants somewhere
704 // TODO: Adjust the PhotoFrame's image size to avoid on the fly scaling
705 Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
706 intent.setType("image/*");
707 intent.putExtra("crop", "true");
708 intent.putExtra("aspectX", 1);
709 intent.putExtra("aspectY", 1);
710 intent.putExtra("outputX", 192);
711 intent.putExtra("outputY", 192);
712 intent.putExtra("noFaceDetection", true);
713 intent.putExtra("return-data", true);
714 return intent;
715 }
716
717 @Override
718 protected void onNewIntent(Intent intent) {
719 super.onNewIntent(intent);
720
721 // Close the menu
722 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
723 getWindow().closeAllPanels();
724
725 try {
726 dismissDialog(DIALOG_CREATE_SHORTCUT);
727 // Unlock the workspace if the dialog was showing
728 mWorkspace.unlock();
729 } catch (Exception e) {
730 // An exception is thrown if the dialog is not visible, which is fine
731 }
732
733 try {
734 dismissDialog(DIALOG_RENAME_FOLDER);
735 // Unlock the workspace if the dialog was showing
736 mWorkspace.unlock();
737 } catch (Exception e) {
738 // An exception is thrown if the dialog is not visible, which is fine
739 }
740
741 // If we are already in front we go back to the default screen,
742 // otherwise we don't
743 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) !=
744 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
745 if (!mWorkspace.isDefaultScreenShowing()) {
746 mWorkspace.moveToDefaultScreen();
747 }
748 closeDrawer();
749 } else {
750 closeDrawer(false);
751 }
752 }
753 }
754
755 @Override
756 protected void onSaveInstanceState(Bundle outState) {
757 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen());
758
759 final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
760 if (folders.size() > 0) {
761 final int count = folders.size();
762 long[] ids = new long[count];
763 for (int i = 0; i < count; i++) {
764 final FolderInfo info = folders.get(i).getInfo();
765 ids[i] = info.id;
766 }
767 outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids);
768 } else {
769 super.onSaveInstanceState(outState);
770 }
771
772 if (mDrawer.isOpened()) {
773 outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
774 }
775
776 if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
777 final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
778 final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen);
779
780 outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen);
781 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX);
782 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY);
783 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX);
784 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY);
785 outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX());
786 outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY());
787 outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS,
788 layout.getOccupiedCells());
789 }
790
791 if (mFolderInfo != null && mWaitingForResult) {
792 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
793 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
794 }
795 }
796
797 @Override
798 public void onDestroy() {
799 mDestroyed = true;
800
801 super.onDestroy();
802
803 TextKeyListener.getInstance().release();
804
805 mAllAppsGrid.clearTextFilter();
806 mAllAppsGrid.setAdapter(null);
807 sModel.unbind();
808 sModel.abortLoaders();
809
810 getContentResolver().unregisterContentObserver(mObserver);
811 unregisterReceiver(mApplicationsReceiver);
812 }
813
814 @Override
815 public void startActivityForResult(Intent intent, int requestCode) {
816 mWaitingForResult = true;
817 super.startActivityForResult(intent, requestCode);
818 }
819
820 @Override
821 public boolean onCreateOptionsMenu(Menu menu) {
822 if (mDesktopLocked) return false;
823
824 super.onCreateOptionsMenu(menu);
825 menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
826 .setIcon(android.R.drawable.ic_menu_add)
827 .setAlphabeticShortcut('A');
828 menu.add(0, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
829 .setIcon(R.drawable.ic_menu_gallery)
830 .setAlphabeticShortcut('W');
831 menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
832 .setIcon(android.R.drawable.ic_search_category_default)
833 .setAlphabeticShortcut(SearchManager.MENU_KEY);
834 menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications)
835 .setIcon(R.drawable.ic_menu_notifications)
836 .setAlphabeticShortcut('N');
837
838 final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
839 settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
840 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
841
842 menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
843 .setIcon(R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
844 .setIntent(settings);
845
846 return true;
847 }
848
849 @Override
850 public boolean onPrepareOptionsMenu(Menu menu) {
851 super.onPrepareOptionsMenu(menu);
852
853 mMenuAddInfo = mWorkspace.findAllVacantCells(null);
854 menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid);
855
856 return true;
857 }
858
859 @Override
860 public boolean onOptionsItemSelected(MenuItem item) {
861 switch (item.getItemId()) {
862 case MENU_ADD:
863 addItems();
864 return true;
865 case MENU_WALLPAPER_SETTINGS:
866 startWallpaper();
867 return true;
868 case MENU_SEARCH:
869 if (!mWorkspace.snapToSearch()) {
870 onSearchRequested();
871 }
872 return true;
873 case MENU_NOTIFICATIONS:
874 showNotifications();
875 return true;
876 }
877
878 return super.onOptionsItemSelected(item);
879 }
880
881 private void addItems() {
882 showAddDialog(mMenuAddInfo);
883 }
884
885 private void removeShortcutsForPackage(String packageName) {
886 if (packageName != null && packageName.length() > 0) {
887 android.util.Log.d(LOG_TAG, packageName);
888 mWorkspace.removeShortcutsForPackage(packageName);
889 }
890 }
891
892 void addShortcut(Intent intent) {
893 startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
894 }
895
896 void addFolder() {
897 UserFolderInfo folderInfo = new UserFolderInfo();
898 folderInfo.title = getText(R.string.folder_name);
899 int cellX = mAddItemCellInfo.cellX;
900 int cellY = mAddItemCellInfo.cellY;
901
902 // Update the model
903 LauncherModel.addItemToDatabase(this, folderInfo, Settings.Favorites.CONTAINER_DESKTOP,
904 mWorkspace.getCurrentScreen(), cellX, cellY, false);
905 sModel.addDesktopItem(folderInfo);
906 sModel.addUserFolder(folderInfo);
907
908 // Create the view
909 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
910 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo);
911 mWorkspace.addInCurrentScreen(newFolder, cellX, cellY, 1, 1);
912 }
913
914 void getPhotoForPhotoFrame() {
915 startActivityForResult(createPhotoPickIntent(), REQUEST_CHOOSE_PHOTO);
916 }
917
918 void addClock() {
919 final Widget info = Widget.makeClock();
920 addWidget(info);
921 }
922
923 void addSearch() {
924 final Widget info = Widget.makeSearch();
925 addWidget(info);
926 }
927
928 private void addWidget(final Widget info) {
929 final CellLayout.CellInfo cellInfo = mAddItemCellInfo;
930
931 final int[] xy = mCellCoordinates;
932 final int spanX = info.spanX;
933 final int spanY = info.spanY;
934
935 if (!findSlot(cellInfo, xy, spanX, spanY)) return;
936
937 sModel.addDesktopItem(info);
938 LauncherModel.addItemToDatabase(this, info, Settings.Favorites.CONTAINER_DESKTOP,
939 mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
940
941 final View view = mInflater.inflate(info.layoutResource, null);
942 view.setTag(info);
943
944 mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
945 }
946
947 private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
948 if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
949 boolean[] occupied = mSavedState != null ?
950 mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
951 cellInfo = mWorkspace.findAllVacantCells(occupied);
952 if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
953 Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
954 return false;
955 }
956 }
957 return true;
958 }
959
960 private void showNotifications() {
961 final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
962 if (statusBar != null) {
963 statusBar.expand();
964 }
965 }
966
967 private void startWallpaper() {
968 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
969 startActivity(Intent.createChooser(pickWallpaper, getString(R.string.chooser_wallpaper)));
970 }
971
972 /**
973 * Registers various intent receivers. The current implementation registers
974 * only a wallpaper intent receiver to let other applications change the
975 * wallpaper.
976 */
977 private void registerIntentReceivers() {
978 if (sWallpaperReceiver == null) {
979 final Application application = getApplication();
980
981 sWallpaperReceiver = new WallpaperIntentReceiver(application, this);
982
983 IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
984 application.registerReceiver(sWallpaperReceiver, filter);
985 } else {
986 sWallpaperReceiver.setLauncher(this);
987 }
988
989 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
990 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
991 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
992 filter.addDataScheme("package");
993 registerReceiver(mApplicationsReceiver, filter);
994 }
995
996 /**
997 * Registers various content observers. The current implementation registers
998 * only a favorites observer to keep track of the favorites applications.
999 */
1000 private void registerContentObservers() {
1001 ContentResolver resolver = getContentResolver();
1002 resolver.registerContentObserver(Settings.Favorites.CONTENT_URI, true, mObserver);
1003 }
1004
1005 @Override
1006 public boolean dispatchKeyEvent(KeyEvent event) {
1007 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1008 switch (event.getKeyCode()) {
1009 case KeyEvent.KEYCODE_BACK:
1010 mWorkspace.dispatchKeyEvent(event);
1011 closeFolder();
1012 closeDrawer();
1013 return true;
1014 case KeyEvent.KEYCODE_HOME:
1015 return true;
1016 }
1017 }
1018
1019 return super.dispatchKeyEvent(event);
1020 }
1021
1022 private void closeDrawer() {
1023 closeDrawer(true);
1024 }
1025
1026 private void closeDrawer(boolean animated) {
1027 if (mDrawer.isOpened()) {
1028 if (animated) {
1029 mDrawer.animateClose();
1030 } else {
1031 mDrawer.close();
1032 }
1033 if (mDrawer.hasFocus()) {
1034 mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1035 }
1036 }
1037 }
1038
1039 private void closeFolder() {
1040 Folder folder = mWorkspace.getOpenFolder();
1041 if (folder != null) {
1042 closeFolder(folder);
1043 }
1044 }
1045
1046 void closeFolder(Folder folder) {
1047 folder.getInfo().opened = false;
1048 ViewGroup parent = (ViewGroup) folder.getParent();
1049 if (parent != null) {
1050 parent.removeView(folder);
1051 }
1052 folder.onClose();
1053 }
1054
1055 /**
1056 * When the notification that favorites have changed is received, requests
1057 * a favorites list refresh.
1058 */
1059 private void onFavoritesChanged() {
1060 mDesktopLocked = true;
1061 mDrawer.lock();
1062 sModel.loadUserItems(false, this);
1063 }
1064
1065 void onDesktopItemsLoaded() {
1066 if (mDestroyed) return;
1067
1068 bindDesktopItems();
1069 mAllAppsGrid.setAdapter(Launcher.getModel().getApplicationsAdapter());
1070
1071 if (mSavedState != null) {
1072 mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1073
1074 final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
1075 if (userFolders != null) {
1076 for (long folderId : userFolders) {
1077 final UserFolderInfo info = sModel.findFolderById(folderId);
1078 if (info != null) {
1079 openUserFolder(info);
1080 }
1081 }
1082 final Folder openFolder = mWorkspace.getOpenFolder();
1083 if (openFolder != null) {
1084 openFolder.requestFocus();
1085 } else {
1086 mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1087 }
1088 }
1089
1090 final boolean allApps = mSavedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false);
1091 if (allApps) {
1092 mDrawer.open();
1093 mDrawer.requestFocus();
1094 }
1095
1096 mSavedState = null;
1097 }
1098
1099 mDesktopLocked = false;
1100 mDrawer.unlock();
1101 }
1102
1103 /**
1104 * Refreshes the shortcuts shown on the workspace.
1105 */
1106 private void bindDesktopItems() {
1107 final ArrayList<ItemInfo> shortcuts = sModel.getDesktopItems();
1108 if (shortcuts == null) {
1109 return;
1110 }
1111
1112 final Workspace workspace = mWorkspace;
1113 int count = workspace.getChildCount();
1114 for (int i = 0; i < count; i++) {
1115 ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
1116 }
1117
1118 count = shortcuts.size();
1119 for (int i = 0; i < count; i++) {
1120 final ItemInfo item = shortcuts.get(i);
1121 switch (item.itemType) {
1122 case Settings.Favorites.ITEM_TYPE_APPLICATION:
1123 case Settings.Favorites.ITEM_TYPE_SHORTCUT:
1124 final View shortcut = createShortcut((ApplicationInfo) item);
1125 workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
1126 !mDesktopLocked);
1127 break;
1128 case Settings.Favorites.ITEM_TYPE_USER_FOLDER:
1129 final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
1130 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()),
1131 ((UserFolderInfo) item));
1132 workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
1133 !mDesktopLocked);
1134 break;
1135 default:
1136 final Widget widget = (Widget)item;
1137 final View view = createWidget(mInflater, widget);
1138 view.setTag(widget);
1139 workspace.addWidget(view, widget, !mDesktopLocked);
1140 }
1141 }
1142
1143 workspace.requestLayout();
1144 }
1145
1146 private View createWidget(LayoutInflater inflater, Widget widget) {
1147 final Workspace workspace = mWorkspace;
1148 final int screen = workspace.getCurrentScreen();
1149 View v = inflater.inflate(widget.layoutResource,
1150 (ViewGroup) workspace.getChildAt(screen), false);
1151 if (widget.itemType == Settings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
1152 ((ImageView)v).setImageBitmap(widget.photo);
1153 }
1154 return v;
1155 }
1156
1157 DragController getDragController() {
1158 return mDragLayer;
1159 }
1160
1161 /**
1162 * Launches the intent referred by the clicked shortcut.
1163 *
1164 * @param v The view representing the clicked shortcut.
1165 */
1166 public void onClick(View v) {
1167 Object tag = v.getTag();
1168 if (tag instanceof ApplicationInfo) {
1169 // Open shortcut
1170 final Intent intent = ((ApplicationInfo) tag).intent;
1171 startActivitySafely(intent);
1172 } else if (tag instanceof UserFolderInfo) {
1173 handleFolderClick((UserFolderInfo) tag);
1174 }
1175 }
1176
1177 void startActivitySafely(Intent intent) {
1178 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1179 try {
1180 startActivity(intent);
1181 } catch (ActivityNotFoundException e) {
1182 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1183 }
1184 }
1185
1186 private void handleFolderClick(FolderInfo folderInfo) {
1187 if (!folderInfo.opened) {
1188 // Close any open folder
1189 closeFolder();
1190 // Open the requested folder
1191 openUserFolder(folderInfo);
1192 } else {
1193 // Find the open folder...
1194 Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
1195 int folderScreen;
1196 if (openFolder != null) {
1197 folderScreen = mWorkspace.getScreenForView(openFolder);
1198 // .. and close it
1199 closeFolder(openFolder);
1200 if (folderScreen != mWorkspace.getCurrentScreen()) {
1201 // Close any folder open on the current screen
1202 closeFolder();
1203 // Pull the folder onto this screen
1204 openUserFolder(folderInfo);
1205 }
1206 }
1207 }
1208 }
1209
1210 private void loadWallpaper() {
1211 // The first time the application is started, we load the wallpaper from
1212 // the ApplicationContext
1213 if (sWallpaper == null) {
1214 final Drawable drawable = getWallpaper();
1215 if (drawable instanceof BitmapDrawable) {
1216 sWallpaper = ((BitmapDrawable) drawable).getBitmap();
1217 } else {
1218 throw new IllegalStateException("The wallpaper must be a BitmapDrawable.");
1219 }
1220 }
1221 mWorkspace.loadWallpaper(sWallpaper);
1222 }
1223
1224 /**
1225 * Opens the user fodler described by the specified tag. The opening of the folder
1226 * is animated relative to the specified View. If the View is null, no animation
1227 * is played.
1228 *
1229 * @param tag The UserFolderInfo describing the folder to open.
1230 */
1231 private void openUserFolder(Object tag) {
1232 UserFolder openFolder = UserFolder.fromXml(this);
1233 openFolder.setDragger(mDragLayer);
1234 openFolder.setLauncher(this);
1235
1236 UserFolderInfo folderInfo = (UserFolderInfo) tag;
1237 openFolder.bind(folderInfo);
1238 folderInfo.opened = true;
1239
1240 mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4);
1241 openFolder.onOpen();
1242 }
1243
1244 /**
1245 * Returns true if the workspace is being loaded. When the workspace is loading,
1246 * no user interaction should be allowed to avoid any conflict.
1247 *
1248 * @return True if the workspace is locked, false otherwise.
1249 */
1250 boolean isWorkspaceLocked() {
1251 return mDesktopLocked;
1252 }
1253
1254 public boolean onLongClick(View v) {
1255 if (mDesktopLocked) {
1256 return false;
1257 }
1258
1259 if (!(v instanceof CellLayout)) {
1260 v = (View) v.getParent();
1261 }
1262
1263 CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
1264
1265 // This happens when long clicking an item with the dpad/trackball
1266 if (cellInfo == null) {
1267 return false;
1268 }
1269
1270 if (mWorkspace.allowLongPress()) {
1271 if (cellInfo.cell == null) {
1272 if (cellInfo.valid) {
1273 // User long pressed on empty space
1274 showAddDialog(cellInfo);
1275 }
1276 } else {
1277 if (!(cellInfo.cell instanceof Folder)) {
1278 // User long pressed on an item
1279 mWorkspace.startDrag(cellInfo);
1280 }
1281 }
1282 }
1283 return true;
1284 }
1285
1286 static LauncherModel getModel() {
1287 return sModel;
1288 }
1289
1290 void closeAllApplications() {
1291 mDrawer.close();
1292 }
1293
1294 boolean isDrawerDown() {
1295 return !mDrawer.isMoving() && !mDrawer.isOpened();
1296 }
1297
1298 Workspace getWorkspace() {
1299 return mWorkspace;
1300 }
1301
1302 @Override
1303 protected Dialog onCreateDialog(int id) {
1304 switch (id) {
1305 case DIALOG_CREATE_SHORTCUT:
1306 return new CreateShortcut().createDialog();
1307 case DIALOG_RENAME_FOLDER:
1308 return new RenameFolder().createDialog();
1309 }
1310
1311 return super.onCreateDialog(id);
1312 }
1313
1314 @Override
1315 protected void onPrepareDialog(int id, Dialog dialog) {
1316 switch (id) {
1317 case DIALOG_CREATE_SHORTCUT:
1318 mWorkspace.lock();
1319 break;
1320 case DIALOG_RENAME_FOLDER:
1321 mWorkspace.lock();
1322 EditText input = (EditText) dialog.findViewById(R.id.folder_name);
1323 final CharSequence text = mFolderInfo.title;
1324 input.setText(text);
1325 input.setSelection(0, text.length());
1326 break;
1327 }
1328 }
1329
1330 void showRenameDialog(UserFolderInfo info) {
1331 mFolderInfo = info;
1332 mWaitingForResult = true;
1333 showDialog(DIALOG_RENAME_FOLDER);
1334 }
1335
1336 private void showAddDialog(CellLayout.CellInfo cellInfo) {
1337 mAddItemCellInfo = cellInfo;
1338 mWaitingForResult = true;
1339 showDialog(DIALOG_CREATE_SHORTCUT);
1340 }
1341
1342 private class RenameFolder {
1343 private EditText mInput;
1344
1345 Dialog createDialog() {
1346 mWaitingForResult = true;
1347 final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
1348 mInput = (EditText) layout.findViewById(R.id.folder_name);
1349
1350 AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1351 builder.setIcon(0);
1352 builder.setTitle(getString(R.string.rename_folder_title));
1353 builder.setCancelable(true);
1354 builder.setOnCancelListener(new Dialog.OnCancelListener() {
1355 public void onCancel(DialogInterface dialog) {
1356 cleanup();
1357 }
1358 });
1359 builder.setNegativeButton(getString(R.string.cancel_action),
1360 new Dialog.OnClickListener() {
1361 public void onClick(DialogInterface dialog, int which) {
1362 cleanup();
1363 }
1364 }
1365 );
1366 builder.setPositiveButton(getString(R.string.rename_action),
1367 new Dialog.OnClickListener() {
1368 public void onClick(DialogInterface dialog, int which) {
1369 changeFolderName();
1370 }
1371 }
1372 );
1373 builder.setView(layout);
1374 return builder.create();
1375 }
1376
1377 private void changeFolderName() {
1378 final String name = mInput.getText().toString();
1379 if (!TextUtils.isEmpty(name)) {
1380 // Make sure we have the right folder info
1381 mFolderInfo = sModel.findFolderById(mFolderInfo.id);
1382 mFolderInfo.title = name;
1383 LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
1384
1385 if (mDesktopLocked) {
1386 mDrawer.lock();
1387 sModel.loadUserItems(false, Launcher.this);
1388 } else {
1389 final FolderIcon folderIcon = (FolderIcon) mWorkspace.getViewForTag(mFolderInfo);
1390 if (folderIcon != null) {
1391 folderIcon.setText(name);
1392 getWorkspace().requestLayout();
1393 } else {
1394 mDesktopLocked = true;
1395 mDrawer.lock();
1396 sModel.loadUserItems(false, Launcher.this);
1397 }
1398 }
1399 }
1400 cleanup();
1401 }
1402
1403 private void cleanup() {
1404 mWorkspace.unlock();
1405 dismissDialog(DIALOG_RENAME_FOLDER);
1406 mWaitingForResult = false;
1407 mFolderInfo = null;
1408 }
1409 }
1410
1411 /**
1412 * Displays the shortcut creation dialog and launches, if necessary, the
1413 * appropriate activity.
1414 */
1415 private class CreateShortcut implements ExpandableListView.OnChildClickListener,
1416 DialogInterface.OnCancelListener, ExpandableListView.OnGroupExpandListener {
1417 private AddAdapter mAdapter;
1418 private ExpandableListView mList;
1419
1420 Dialog createDialog() {
1421 mWaitingForResult = true;
1422 mAdapter = new AddAdapter(Launcher.this, false);
1423
1424 final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1425 builder.setTitle(getString(R.string.menu_item_add_item));
1426 builder.setIcon(0);
1427
1428 mList = (ExpandableListView)
1429 View.inflate(Launcher.this, R.layout.create_shortcut_list, null);
1430 mList.setAdapter(mAdapter);
1431 mList.setOnChildClickListener(this);
1432 mList.setOnGroupExpandListener(this);
1433 builder.setView(mList);
1434 builder.setInverseBackgroundForced(true);
1435
1436 AlertDialog dialog = builder.create();
1437 dialog.setOnCancelListener(this);
1438
1439 WindowManager.LayoutParams attributes = dialog.getWindow().getAttributes();
1440 attributes.gravity = Gravity.TOP;
1441 dialog.onWindowAttributesChanged(attributes);
1442
1443 return dialog;
1444 }
1445
1446 public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
1447 int childPosition, long id) {
1448 mAdapter.performAction(groupPosition, childPosition);
1449 cleanup();
1450 return true;
1451 }
1452
1453 public void onCancel(DialogInterface dialog) {
1454 mWaitingForResult = false;
1455 cleanup();
1456 }
1457
1458 private void cleanup() {
1459 mWorkspace.unlock();
1460 dismissDialog(DIALOG_CREATE_SHORTCUT);
1461 }
1462
1463 public void onGroupExpand(int groupPosition) {
1464 long packged = ExpandableListView.getPackedPositionForGroup(groupPosition);
1465 int position = mList.getFlatListPosition(packged);
1466 mList.setSelectionFromTop(position, 0);
1467 }
1468 }
1469
1470 /**
1471 * Receives notifications when applications are added/removed.
1472 */
1473 private class ApplicationsIntentReceiver extends BroadcastReceiver {
1474 @Override
1475 public void onReceive(Context context, Intent intent) {
1476 //noinspection ConstantConditions
1477 if (REMOVE_SHORTCUT_ON_PACKAGE_REMOVE &&
1478 Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
1479 removeShortcutsForPackage(intent.getData().getSchemeSpecificPart());
1480 }
1481 removeDialog(DIALOG_CREATE_SHORTCUT);
1482 sModel.loadApplications(false, Launcher.this);
1483 }
1484 }
1485
1486 /**
1487 * Receives notifications whenever the user favorites have changed.
1488 */
1489 private class FavoritesChangeObserver extends ContentObserver {
1490 public FavoritesChangeObserver() {
1491 super(mHandler);
1492 }
1493
1494 @Override
1495 public void onChange(boolean selfChange) {
1496 onFavoritesChanged();
1497 }
1498 }
1499
1500 private class SensorHandler implements SensorListener {
1501 private long mLastNegativeShake;
1502 private long mLastPositiveShake;
1503
1504 public void onSensorChanged(int sensor, float[] values) {
1505 if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
1506 float shake = values[0];
1507 if (shake <= -SensorManager.STANDARD_GRAVITY) {
1508 mLastNegativeShake = SystemClock.uptimeMillis();
1509 } else if (shake >= SensorManager.STANDARD_GRAVITY) {
1510 mLastPositiveShake = SystemClock.uptimeMillis();
1511 }
1512
1513 final long difference = mLastPositiveShake - mLastNegativeShake;
1514 if (difference <= -80 && difference >= -180) {
1515 mWorkspace.scrollLeft();
1516 mLastNegativeShake = mLastPositiveShake = 0;
1517 } else if (difference >= 80 && difference <= 180) {
1518 mWorkspace.scrollRight();
1519 mLastNegativeShake = mLastPositiveShake = 0;
1520 }
1521 }
1522 }
1523
1524 public void onAccuracyChanged(int sensor, int accuracy) {
1525 }
1526 }
1527
1528 /**
1529 * Receives intents from other applications to change the wallpaper.
1530 */
1531 private static class WallpaperIntentReceiver extends BroadcastReceiver {
1532 private final Application mApplication;
1533 private WeakReference<Launcher> mLauncher;
1534
1535 WallpaperIntentReceiver(Application application, Launcher launcher) {
1536 mApplication = application;
1537 setLauncher(launcher);
1538 }
1539
1540 void setLauncher(Launcher launcher) {
1541 mLauncher = new WeakReference<Launcher>(launcher);
1542 }
1543
1544 @Override
1545 public void onReceive(Context context, Intent intent) {
1546 // Load the wallpaper from the ApplicationContext and store it locally
1547 // until the Launcher Activity is ready to use it
1548 final Drawable drawable = mApplication.getWallpaper();
1549 if (drawable instanceof BitmapDrawable) {
1550 sWallpaper = ((BitmapDrawable) drawable).getBitmap();
1551 } else {
1552 throw new IllegalStateException("The wallpaper must be a BitmapDrawable.");
1553 }
1554
1555 // If Launcher is alive, notify we have a new wallpaper
1556 if (mLauncher != null) {
1557 final Launcher launcher = mLauncher.get();
1558 if (launcher != null) {
1559 launcher.loadWallpaper();
1560 }
1561 }
1562 }
1563 }
1564
1565 private class DrawerManager implements SlidingDrawer.OnDrawerOpenListener,
1566 SlidingDrawer.OnDrawerCloseListener, SlidingDrawer.OnDrawerScrollListener {
1567 private boolean mOpen;
1568
1569 public void onDrawerOpened() {
1570 if (!mOpen) {
1571 mHandleIcon.reverseTransition(150);
1572 mOpen = true;
1573 }
1574 }
1575
1576 public void onDrawerClosed() {
1577 if (mOpen) {
1578 mHandleIcon.reverseTransition(150);
1579 mOpen = false;
1580 }
1581 mAllAppsGrid.setSelection(0);
1582 mAllAppsGrid.clearTextFilter();
1583 mWorkspace.clearChildrenCache();
1584 }
1585
1586 public void onScrollStarted() {
1587 mWorkspace.enableChildrenCache();
1588 }
1589
1590 public void onScrollEnded() {
1591 }
1592 }
1593}