diff options
Diffstat (limited to 'src/com/android/launcher2/Launcher.java')
-rw-r--r-- | src/com/android/launcher2/Launcher.java | 964 |
1 files changed, 836 insertions, 128 deletions
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index 28a671263..c5450f34e 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -1,3 +1,4 @@ + /* * Copyright (C) 2008 The Android Open Source Project * @@ -16,14 +17,23 @@ package com.android.launcher2; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import com.android.common.Search; +import com.android.launcher.R; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.AnimatorSet; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.SearchManager; import android.app.StatusBarManager; import android.app.WallpaperManager; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -35,16 +45,18 @@ import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Rect; import android.graphics.Canvas; -import android.graphics.drawable.Drawable; +import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -53,6 +65,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.LiveFolders; +import android.provider.Settings; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -64,34 +77,40 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; import android.widget.ImageView; -import android.widget.PopupWindow; import android.widget.LinearLayout; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.TabHost; +import android.widget.TabHost.OnTabChangeListener; +import android.widget.TabHost.TabContentFactory; +import android.widget.TabWidget; +import android.widget.TextView; +import android.widget.Toast; -import java.util.ArrayList; -import java.util.List; -import java.util.HashMap; +import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.DataInputStream; - -import com.android.launcher.R; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; /** * Default launcher application. */ public final class Launcher extends Activity - implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher { + implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, + AllAppsView.Watcher, View.OnTouchListener { static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -123,8 +142,6 @@ public final class Launcher extends Activity static final int SCREEN_COUNT = 5; static final int DEFAULT_SCREEN = 2; - static final int NUMBER_CELLS_X = 4; - static final int NUMBER_CELLS_Y = 4; static final int DIALOG_CREATE_SHORTCUT = 1; static final int DIALOG_RENAME_FOLDER = 2; @@ -158,6 +175,17 @@ public final class Launcher extends Activity // Type: long private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; + // tags for the customization tabs + private static final String WIDGETS_TAG = "widgets"; + private static final String FOLDERS_TAG = "folders"; + private static final String SHORTCUTS_TAG = "shortcuts"; + private static final String WALLPAPERS_TAG = "wallpapers"; + + private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; + + /** The different states that Launcher can be in. */ + private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW }; + static final int APPWIDGET_HOST_ID = 1024; private static final Object sLock = new Object(); @@ -176,6 +204,7 @@ public final class Launcher extends Activity private LauncherAppWidgetHost mAppWidgetHost; private CellLayout.CellInfo mAddItemCellInfo; + private int[] mAddItemCoordinates; private CellLayout.CellInfo mMenuAddInfo; private final int[] mCellCoordinates = new int[2]; private FolderInfo mFolderInfo; @@ -183,6 +212,10 @@ public final class Launcher extends Activity private DeleteZone mDeleteZone; private HandleView mHandleView; private AllAppsView mAllAppsGrid; + private TabHost mHomeCustomizationDrawer; + + private PagedView mAllAppsPagedView = null; + private CustomizePagedView mCustomizePagedView = null; private Bundle mSavedState; @@ -208,16 +241,23 @@ public final class Launcher extends Activity private ImageView mNextView; // Hotseats (quick-launch icons next to AllApps) - private static final int NUM_HOTSEATS = 2; private String[] mHotseatConfig = null; private Intent[] mHotseats = null; private Drawable[] mHotseatIcons = null; private CharSequence[] mHotseatLabels = null; + private Intent mAppMarketIntent = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (LauncherApplication.isInPlaceRotationEnabled()) { + // hide the status bar (temporary until we get the status bar design figured out) + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + LauncherApplication app = ((LauncherApplication)getApplication()); mModel = app.setLauncher(this); mIconCache = app.getIconCache(); @@ -235,8 +275,80 @@ public final class Launcher extends Activity loadHotseats(); checkForLocaleChange(); setWallpaperDimension(); - setContentView(R.layout.launcher); + mHomeCustomizationDrawer = (TabHost) findViewById(com.android.internal.R.id.tabhost); + if (mHomeCustomizationDrawer != null) { + mHomeCustomizationDrawer.setup(); + + // share the same customization workspace across all the tabs + mCustomizePagedView = new CustomizePagedView(this); + TabContentFactory contentFactory = new TabContentFactory() { + public View createTabContent(String tag) { + return mCustomizePagedView; + } + }; + + String widgetsLabel = getString(R.string.widgets_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WIDGETS_TAG) + .setIndicator(widgetsLabel).setContent(contentFactory)); + String foldersLabel = getString(R.string.folders_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(FOLDERS_TAG) + .setIndicator(foldersLabel).setContent(contentFactory)); + String shortcutsLabel = getString(R.string.shortcuts_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(SHORTCUTS_TAG) + .setIndicator(shortcutsLabel).setContent(contentFactory)); + String wallpapersLabel = getString(R.string.wallpapers_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WALLPAPERS_TAG) + .setIndicator(wallpapersLabel).setContent(contentFactory)); + + // TEMP: just styling the tab widget to be a bit nicer until we get the actual + // new assets + TabWidget tabWidget = mHomeCustomizationDrawer.getTabWidget(); + for (int i = 0; i < tabWidget.getChildCount(); ++i) { + RelativeLayout tab = (RelativeLayout) tabWidget.getChildTabViewAt(i); + TextView text = (TextView) tab.getChildAt(1); + text.setTextSize(20.0f); + text.setPadding(20, 0, 20, 0); + text.setShadowLayer(1.0f, 0.0f, 1.0f, Color.BLACK); + tab.setBackgroundDrawable(null); + } + + mHomeCustomizationDrawer.setOnTabChangedListener(new OnTabChangeListener() { + public void onTabChanged(String tabId) { + // animate the changing of the tab content by fading pages in and out + final int duration = 150; + final float alpha = mCustomizePagedView.getAlpha(); + ValueAnimator alphaAnim = new ObjectAnimator(duration, mCustomizePagedView, + "alpha", alpha, 0.0f); + alphaAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + String tag = mHomeCustomizationDrawer.getCurrentTabTag(); + if (tag == WIDGETS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.WidgetCustomization); + } else if (tag == FOLDERS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.FolderCustomization); + } else if (tag == SHORTCUTS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.ShortcutCustomization); + } else if (tag == WALLPAPERS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.WallpaperCustomization); + } + + final float alpha = mCustomizePagedView.getAlpha(); + ValueAnimator alphaAnim = new ObjectAnimator(duration, mCustomizePagedView, + "alpha", alpha, 1.0f); + alphaAnim.start(); + } + }); + alphaAnim.start(); + } + }); + + mHomeCustomizationDrawer.setCurrentTab(0); + } setupViews(); registerContentObservers(); @@ -379,11 +491,12 @@ public final class Launcher extends Activity WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE); Display display = getWindowManager().getDefaultDisplay(); - boolean isPortrait = display.getWidth() < display.getHeight(); - - final int width = isPortrait ? display.getWidth() : display.getHeight(); - final int height = isPortrait ? display.getHeight() : display.getWidth(); - wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height); + // TODO: Put back when we decide about scrolling the wallpaper + // boolean isPortrait = display.getWidth() < display.getHeight(); + // final int width = isPortrait ? display.getWidth() : display.getHeight(); + // final int height = isPortrait ? display.getHeight() : display.getWidth(); + wpm.suggestDesiredDimensions(Math.max(display.getWidth(), display.getHeight()), + Math.max(display.getWidth(), display.getHeight())); } // Note: This doesn't do all the client-id magic that BrowserProvider does @@ -442,7 +555,7 @@ public final class Launcher extends Activity // note: if the user launches this without a default set, she // will always be taken to the default URL above; this is // unavoidable as we must specify a valid URL in order for the - // chooser to appear, and once the user selects something, that + // chooser to appear, and once the user selects something, that // URL is unavoidably sent to the chosen app. } else { try { @@ -452,7 +565,7 @@ public final class Launcher extends Activity // bogus; leave intent=null } } - + if (intent == null) { mHotseats[i] = null; mHotseatLabels[i] = getText(R.string.activity_not_found); @@ -460,15 +573,15 @@ public final class Launcher extends Activity } if (LOGD) { - Log.d(TAG, "loadHotseats: hotseat " + i - + " initial intent=[" + Log.d(TAG, "loadHotseats: hotseat " + i + + " initial intent=[" + intent.toUri(Intent.URI_INTENT_SCHEME) + "]"); } ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - if (LOGD) { + if (LOGD) { Log.d(TAG, "Best match for intent: " + bestMatch); Log.d(TAG, "All matches: "); for (ResolveInfo ri : allMatches) { @@ -477,8 +590,8 @@ public final class Launcher extends Activity } // did this resolve to a single app, or the resolver? if (allMatches.size() == 0 || bestMatch == null) { - // can't find any activity to handle this. let's leave the - // intent as-is and let Launcher show a toast when it fails + // can't find any activity to handle this. let's leave the + // intent as-is and let Launcher show a toast when it fails // to launch. mHotseats[i] = intent; @@ -494,7 +607,7 @@ public final class Launcher extends Activity break; } } - + if (!found) { if (LOGD) Log.d(TAG, "Multiple options, no default yet"); // the bestMatch is probably the ResolveActivity, meaning the @@ -519,8 +632,8 @@ public final class Launcher extends Activity } if (LOGD) { - Log.d(TAG, "loadHotseats: hotseat " + i - + " final intent=[" + Log.d(TAG, "loadHotseats: hotseat " + i + + " final intent=[" + ((mHotseats[i] == null) ? "null" : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME)) @@ -559,10 +672,11 @@ public final class Launcher extends Activity completeAddLiveFolder(data, mAddItemCellInfo); break; case REQUEST_PICK_APPWIDGET: - addAppWidget(data); + addAppWidgetFromPick(data); break; case REQUEST_CREATE_APPWIDGET: - completeAddAppWidget(data, mAddItemCellInfo); + int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + completeAddAppWidget(appWidgetId, mAddItemCellInfo); break; case REQUEST_PICK_WALLPAPER: // We just wanted the activity result here so we can clear mWaitingForResult @@ -590,13 +704,21 @@ public final class Launcher extends Activity mModel.startLoader(this, true); mRestoring = false; } + // When we resume Launcher, a different Activity might be responsible for the app + // market intent, so refresh the icon + updateAppMarketIcon(); } @Override protected void onPause() { super.onPause(); - dismissPreview(mPreviousView); - dismissPreview(mNextView); + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + } + if (mNextView != null) { + dismissPreview(mNextView); + } mDragController.cancelDrag(); } @@ -687,11 +809,12 @@ public final class Launcher extends Activity final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); if (currentScreen > -1) { - mWorkspace.setCurrentScreen(currentScreen); + mWorkspace.setCurrentPage(currentScreen); } final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); if (addScreen > -1) { + mAddItemCoordinates = null; mAddItemCellInfo = new CellLayout.CellInfo(); final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo; addItemCellInfo.valid = true; @@ -700,7 +823,7 @@ public final class Launcher extends Activity addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); - addItemCellInfo.findVacantCellsFromOccupied( + addItemCellInfo.updateOccupiedCells( savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS), savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X), savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y)); @@ -719,7 +842,7 @@ public final class Launcher extends Activity * Finds all the views we need and configure them properly. */ private void setupViews() { - DragController dragController = mDragController; + final DragController dragController = mDragController; DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer); dragLayer.setDragController(dragController); @@ -728,8 +851,15 @@ public final class Launcher extends Activity mAllAppsGrid.setLauncher(this); mAllAppsGrid.setDragController(dragController); ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window. - // Manage focusability manually since this thing is always visible - ((View) mAllAppsGrid).setFocusable(false); + // Manage focusability manually since this thing is always visible (in non-xlarge) + ((View) mAllAppsGrid).setFocusable(false); + + if (LauncherApplication.isScreenXLarge()) { + // They need to be INVISIBLE initially so that they will be measured in the layout. + // Otherwise the animations are messed up when we show them for the first time. + ((View) mAllAppsGrid).setVisibility(View.INVISIBLE); + mHomeCustomizationDrawer.setVisibility(View.INVISIBLE); + } mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); final Workspace workspace = mWorkspace; @@ -738,29 +868,39 @@ public final class Launcher extends Activity DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone); mDeleteZone = deleteZone; - mHandleView = (HandleView) findViewById(R.id.all_apps_button); - mHandleView.setLauncher(this); - mHandleView.setOnClickListener(this); - mHandleView.setOnLongClickListener(this); + View handleView = findViewById(R.id.all_apps_button); + if (handleView != null && handleView instanceof HandleView) { + // we don't use handle view in xlarge mode + mHandleView = (HandleView)handleView; + mHandleView.setLauncher(this); + mHandleView.setOnClickListener(this); + mHandleView.setOnLongClickListener(this); + } - ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left); - hotseatLeft.setContentDescription(mHotseatLabels[0]); - hotseatLeft.setImageDrawable(mHotseatIcons[0]); - ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right); - hotseatRight.setContentDescription(mHotseatLabels[1]); - hotseatRight.setImageDrawable(mHotseatIcons[1]); + if (mCustomizePagedView != null) { + mCustomizePagedView.setLauncher(this); + mCustomizePagedView.setDragController(dragController); + mCustomizePagedView.update(); + } else { + ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left); + hotseatLeft.setContentDescription(mHotseatLabels[0]); + hotseatLeft.setImageDrawable(mHotseatIcons[0]); + ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right); + hotseatRight.setContentDescription(mHotseatLabels[1]); + hotseatRight.setImageDrawable(mHotseatIcons[1]); - mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen); - mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen); + mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen); + mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen); - Drawable previous = mPreviousView.getDrawable(); - Drawable next = mNextView.getDrawable(); - mWorkspace.setIndicators(previous, next); + Drawable previous = mPreviousView.getDrawable(); + Drawable next = mNextView.getDrawable(); + mWorkspace.setIndicators(previous, next); - mPreviousView.setHapticFeedbackEnabled(false); - mPreviousView.setOnLongClickListener(this); - mNextView.setHapticFeedbackEnabled(false); - mNextView.setOnLongClickListener(this); + mPreviousView.setHapticFeedbackEnabled(false); + mPreviousView.setOnLongClickListener(this); + mNextView.setHapticFeedbackEnabled(false); + mNextView.setOnLongClickListener(this); + } workspace.setOnLongClickListener(this); workspace.setDragController(dragController); @@ -768,16 +908,33 @@ public final class Launcher extends Activity deleteZone.setLauncher(this); deleteZone.setDragController(dragController); - deleteZone.setHandle(findViewById(R.id.all_apps_button_cluster)); + int deleteZoneHandleId; + if (LauncherApplication.isScreenXLarge()) { + deleteZoneHandleId = R.id.all_apps_button; + } else { + deleteZoneHandleId = R.id.all_apps_button_cluster; + } + deleteZone.setHandle(findViewById(deleteZoneHandleId)); + dragController.addDragListener(deleteZone); + + ApplicationInfoDropTarget infoButton = (ApplicationInfoDropTarget)findViewById(R.id.info_button); + if (infoButton != null) { + infoButton.setLauncher(this); + infoButton.setHandle(findViewById(R.id.configure_button)); + infoButton.setDragColor(getResources().getColor(R.color.app_info_filter)); + dragController.addDragListener(infoButton); + } dragController.setDragScoller(workspace); - dragController.setDragListener(deleteZone); dragController.setScrollView(dragLayer); dragController.setMoveTarget(workspace); // The order here is bottom to top. dragController.addDropTarget(workspace); dragController.addDropTarget(deleteZone); + if (infoButton != null) { + dragController.addDropTarget(infoButton); + } } @SuppressWarnings({"UnusedDeclaration"}) @@ -815,7 +972,7 @@ public final class Launcher extends Activity ); } } - + /** * Creates a view representing a shortcut. * @@ -825,7 +982,7 @@ public final class Launcher extends Activity */ View createShortcut(ShortcutInfo info) { return createShortcut(R.layout.application, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); } /** @@ -857,7 +1014,7 @@ public final class Launcher extends Activity * @param cellInfo The position on screen where to create the shortcut. */ void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); + cellInfo.screen = mWorkspace.getCurrentPage(); if (!findSingleSlot(cellInfo)) return; final ShortcutInfo info = mModel.getShortcutInfo(context.getPackageManager(), @@ -880,7 +1037,7 @@ public final class Launcher extends Activity * @param cellInfo The position on screen where to create the shortcut. */ private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); + cellInfo.screen = mWorkspace.getCurrentPage(); if (!findSingleSlot(cellInfo)) return; final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false); @@ -896,24 +1053,37 @@ public final class Launcher extends Activity /** * Add a widget to the workspace. * - * @param data The intent describing the appWidgetId. + * @param appWidgetId The app widget id * @param cellInfo The position on screen where to create the widget. */ - private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) { - Bundle extras = data.getExtras(); - int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); - - if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString()); - + private void completeAddAppWidget(int appWidgetId, CellLayout.CellInfo cellInfo) { AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); // Calculate the grid spans needed to fit this widget CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen); - int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight); + int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight, null); // Try finding open space on Launcher screen + // We have saved the position to which the widget was dragged-- this really only matters + // if we are placing widgets on a "spring-loaded" screen final int[] xy = mCellCoordinates; - if (!findSlot(cellInfo, xy, spans[0], spans[1])) { + + // For now, we don't save the coordinate where we dropped the icon because we're not + // supporting spring-loaded mini-screens; however, leaving the ability to directly place + // a widget on the home screen in case we want to add it in the future + final int[] xyTouch = null; + //final int[] xyTouch = mAddItemCoordinates; + boolean findNearestVacantAreaFailed = false; + if (xyTouch != null) { + CellLayout screen = (CellLayout) mWorkspace.getChildAt(cellInfo.screen); + int[] result = screen.findNearestVacantArea( + mAddItemCoordinates[0], mAddItemCoordinates[1], + spans[0], spans[1], cellInfo, xy); + findNearestVacantAreaFailed = (result == null); + } + + if (findNearestVacantAreaFailed || + (xyTouch == null && !findSlot(cellInfo, xy, spans[0], spans[1]))) { if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId); return; } @@ -925,7 +1095,7 @@ public final class Launcher extends Activity LauncherModel.addItemToDatabase(this, launcherInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), xy[0], xy[1], false); + cellInfo.screen, xy[0], xy[1], false); if (!mRestoring) { mDesktopItems.add(launcherInfo); @@ -936,7 +1106,7 @@ public final class Launcher extends Activity launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); launcherInfo.hostView.setTag(launcherInfo); - mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1], + mWorkspace.addInScreen(launcherInfo.hostView, cellInfo.screen, xy[0], xy[1], launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); } } @@ -983,10 +1153,19 @@ public final class Launcher extends Activity boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); boolean allAppsVisible = isAllAppsVisible(); - if (!mWorkspace.isDefaultScreenShowing()) { - mWorkspace.moveToDefaultScreen(alreadyOnHome && !allAppsVisible); + boolean customizationDrawerVisible = isCustomizationDrawerVisible(); + + // in all these cases, only animate if we're already on home + if (LauncherApplication.isScreenXLarge()) { + mWorkspace.unshrink(alreadyOnHome); + } + if (!mWorkspace.isDefaultPageShowing()) { + // on the phone, we don't animate the change to the workspace if all apps is visible + mWorkspace.moveToDefaultScreen( + alreadyOnHome && (LauncherApplication.isScreenXLarge() || !allAppsVisible)); } closeAllApps(alreadyOnHome && allAppsVisible); + hideCustomizationDrawer(alreadyOnHome); final View v = getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { @@ -1001,11 +1180,18 @@ public final class Launcher extends Activity protected void onRestoreInstanceState(Bundle savedInstanceState) { // Do not call super here mSavedInstanceState = savedInstanceState; + + if (mHomeCustomizationDrawer != null) { + String cur = savedInstanceState.getString("currentTab"); + if (cur != null) { + mHomeCustomizationDrawer.setCurrentTabByTag(cur); + } + } } @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen()); + outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); final ArrayList<Folder> folders = mWorkspace.getOpenFolders(); if (folders.size() > 0) { @@ -1037,13 +1223,20 @@ public final class Launcher extends Activity outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX()); outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY()); outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS, - layout.getOccupiedCells()); + layout.getOccupiedCellsFlattened()); } if (mFolderInfo != null && mWaitingForResult) { outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); } + + if (mHomeCustomizationDrawer != null) { + String currentTabTag = mHomeCustomizationDrawer.getCurrentTabTag(); + if (currentTabTag != null) { + outState.putString("currentTab", currentTabTag); + } + } } @Override @@ -1063,9 +1256,14 @@ public final class Launcher extends Activity unbindDesktopItems(); getContentResolver().unregisterContentObserver(mWidgetObserver); - - dismissPreview(mPreviousView); - dismissPreview(mNextView); + + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + } + if (mNextView != null) { + dismissPreview(mNextView); + } unregisterReceiver(mCloseSystemDialogsReceiver); } @@ -1136,24 +1334,31 @@ public final class Launcher extends Activity // If all apps is animating, don't show the menu, because we don't know // which one to show. - if (mAllAppsGrid.isVisible() && !mAllAppsGrid.isOpaque()) { + if (mAllAppsGrid.isAnimating()) { return false; } // Only show the add and wallpaper options when we're not in all apps. - boolean visible = !mAllAppsGrid.isOpaque(); + boolean visible = !mAllAppsGrid.isVisible(); menu.setGroupVisible(MENU_GROUP_ADD, visible); menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible); // Disable add if the workspace is full. if (visible) { - mMenuAddInfo = mWorkspace.findAllVacantCells(null); + mMenuAddInfo = mWorkspace.updateOccupiedCellsForCurrentScreen(null); menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid); } return true; } + // we need to initialize mAddItemCellInfo before adding something to the homescreen -- when + // using the settings menu to add an item, something similar happens in showAddDialog + public void prepareAddItemFromHomeCustomizationDrawer() { + mMenuAddInfo = mWorkspace.updateOccupiedCellsForCurrentScreen(null); + mAddItemCellInfo = mMenuAddInfo; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -1190,13 +1395,37 @@ public final class Launcher extends Activity } private void addItems() { - closeAllApps(true); - showAddDialog(mMenuAddInfo); + if (LauncherApplication.isScreenXLarge()) { + // Animate the widget chooser up from the bottom of the screen + if (!isCustomizationDrawerVisible()) { + showCustomizationDrawer(true); + } + } else { + closeAllApps(true); + showAddDialog(mMenuAddInfo); + } } - void addAppWidget(Intent data) { + void addAppWidgetFromDrop(ComponentName appWidgetProvider, CellLayout.CellInfo cellInfo, + int[] position) { + mAddItemCellInfo = cellInfo; + + // only set mAddItemCoordinates if we dropped on home screen in "spring-loaded" manner + mAddItemCoordinates = position; + int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); + AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, appWidgetProvider); + addAppWidgetImpl(appWidgetId); + } + + void addAppWidgetFromPick(Intent data) { // TODO: catch bad widget exception when sent int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + // TODO: Is this log message meaningful? + if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras()); + addAppWidgetImpl(appWidgetId); + } + + void addAppWidgetImpl(int appWidgetId) { AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId); if (appWidget.configure != null) { @@ -1208,7 +1437,7 @@ public final class Launcher extends Activity startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); } else { // Otherwise just add it - onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data); + completeAddAppWidget(appWidgetId, mAddItemCellInfo); } } @@ -1246,18 +1475,18 @@ public final class Launcher extends Activity folderInfo.title = getText(R.string.folder_name); CellLayout.CellInfo cellInfo = mAddItemCellInfo; - cellInfo.screen = mWorkspace.getCurrentScreen(); + cellInfo.screen = mWorkspace.getCurrentPage(); if (!findSingleSlot(cellInfo)) return; // Update the model LauncherModel.addItemToDatabase(this, folderInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false); + mWorkspace.getCurrentPage(), cellInfo.cellX, cellInfo.cellY, false); sFolders.put(folderInfo.id, folderInfo); // Create the view FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), folderInfo); mWorkspace.addInCurrentScreen(newFolder, cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked()); } @@ -1267,14 +1496,14 @@ public final class Launcher extends Activity } private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); + cellInfo.screen = mWorkspace.getCurrentPage(); if (!findSingleSlot(cellInfo)) return; final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false); if (!mRestoring) { final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked()); } @@ -1335,9 +1564,8 @@ public final class Launcher extends Activity private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) { if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { - boolean[] occupied = mSavedState != null ? - mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null; - cellInfo = mWorkspace.findAllVacantCells(occupied); + CellLayout targetLayout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen); + cellInfo = targetLayout.updateOccupiedCells(null, null); if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); return false; @@ -1409,11 +1637,16 @@ public final class Launcher extends Activity public void onBackPressed() { if (isAllAppsVisible()) { closeAllApps(true); + } else if (isCustomizationDrawerVisible()) { + hideCustomizationDrawer(true); } else { closeFolder(); } - dismissPreview(mPreviousView); - dismissPreview(mNextView); + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + dismissPreview(mNextView); + } } private void closeFolder() { @@ -1479,6 +1712,74 @@ public final class Launcher extends Activity } } + public boolean onTouch(View v, MotionEvent event) { + // this is an intercepted event being forwarded from mWorkspace; + // clicking anywhere on the workspace causes the drawer to slide down + hideCustomizationDrawer(true); + return false; + } + + /** + * Event handler for the search button + * + * @param v The view that was clicked. + */ + public void onClickSearchButton(View v) { + Intent i = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + View button = findViewById(R.id.search_button); + i.setSourceBounds( + new Rect(button.getLeft(), button.getTop(), button.getRight(), button.getBottom())); + startActivity(i); + } + + /** + * Event handler for the "gear" button that appears on the home screen, which + * enters home screen customization mode. + * + * @param v The view that was clicked. + */ + public void onClickConfigureButton(View v) { + addItems(); + } + + /** + * Event handler for the "grid" button that appears on the home screen, which + * enters all apps mode. + * + * @param v The view that was clicked. + */ + public void onClickAllAppsButton(View v) { + showAllApps(true); + } + + public void onClickAppMarketButton(View v) { + if (mAppMarketIntent != null) { + startActivitySafely(mAppMarketIntent, "app market"); + } + } + + void startApplicationDetailsActivity(ComponentName componentName) { + String packageName = componentName.getPackageName(); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", packageName, null)); + startActivity(intent); + } + + void startApplicationUninstallActivity(ApplicationInfo appInfo) { + if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) { + // System applications cannot be installed. For now, show a toast explaining that. + // We may give them the option of disabling apps this way. + int messageId = R.string.uninstall_system_app_text; + Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show(); + } else { + String packageName = appInfo.componentName.getPackageName(); + String className = appInfo.componentName.getClassName(); + Intent intent = new Intent( + Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className)); + startActivity(intent); + } + } + void startActivitySafely(Intent intent, Object tag) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { @@ -1494,7 +1795,7 @@ public final class Launcher extends Activity + "tag="+ tag + " intent=" + intent, e); } } - + void startActivityForResultSafely(Intent intent, int requestCode) { try { startActivityForResult(intent, requestCode); @@ -1519,10 +1820,10 @@ public final class Launcher extends Activity Folder openFolder = mWorkspace.getFolderForTag(folderInfo); int folderScreen; if (openFolder != null) { - folderScreen = mWorkspace.getScreenForView(openFolder); + folderScreen = mWorkspace.getPageForView(openFolder); // .. and close it closeFolder(openFolder); - if (folderScreen != mWorkspace.getCurrentScreen()) { + if (folderScreen != mWorkspace.getCurrentPage()) { // Close any folder open on the current screen closeFolder(); // Pull the folder onto this screen @@ -1539,7 +1840,7 @@ public final class Launcher extends Activity * * @param folderInfo The FolderInfo describing the folder to open. */ - private void openFolder(FolderInfo folderInfo) { + public void openFolder(FolderInfo folderInfo) { Folder openFolder; if (folderInfo instanceof UserFolderInfo) { @@ -1556,7 +1857,8 @@ public final class Launcher extends Activity openFolder.bind(folderInfo); folderInfo.opened = true; - mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4); + mWorkspace.addInFullScreen(openFolder, folderInfo.screen); + openFolder.onOpen(); } @@ -1654,9 +1956,9 @@ public final class Launcher extends Activity final Workspace workspace = mWorkspace; CellLayout cell = ((CellLayout) workspace.getChildAt(start)); - + float max = workspace.getChildCount(); - + final Rect r = new Rect(); resources.getDrawable(R.drawable.preview_background).getPadding(r); int extraW = (int) ((r.left + r.right) * max); @@ -1702,12 +2004,12 @@ public final class Launcher extends Activity image.setOnClickListener(handler); image.setOnFocusChangeListener(handler); image.setFocusable(true); - if (i == mWorkspace.getCurrentScreen()) image.requestFocus(); + if (i == mWorkspace.getCurrentPage()) image.requestFocus(); preview.addView(image, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - bitmaps.add(bitmap); + bitmaps.add(bitmap); } final PopupWindow p = new PopupWindow(this); @@ -1728,7 +2030,7 @@ public final class Launcher extends Activity anchor.setTag(p); anchor.setTag(R.id.workspace, preview); - anchor.setTag(R.id.icon, bitmaps); + anchor.setTag(R.id.icon, bitmaps); } class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener { @@ -1739,17 +2041,17 @@ public final class Launcher extends Activity } public void onClick(View v) { - mWorkspace.snapToScreen((Integer) v.getTag()); + mWorkspace.snapToPage((Integer) v.getTag()); v.post(this); } public void run() { - dismissPreview(mAnchor); + dismissPreview(mAnchor); } public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { - mWorkspace.snapToScreen((Integer) v.getTag()); + mWorkspace.snapToPage((Integer) v.getTag()); } } } @@ -1794,11 +2096,13 @@ public final class Launcher extends Activity private void showAddDialog(CellLayout.CellInfo cellInfo) { mAddItemCellInfo = cellInfo; + mAddItemCoordinates = null; mWaitingForResult = true; showDialog(DIALOG_CREATE_SHORTCUT); } private void pickShortcut() { + // Insert extra item to handle picking application Bundle bundle = new Bundle(); ArrayList<String> shortcutNames = new ArrayList<String>(); @@ -1900,22 +2204,319 @@ public final class Launcher extends Activity // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. public boolean isAllAppsVisible() { - return (mAllAppsGrid != null) ? mAllAppsGrid.isVisible() : false; + return mAllAppsGrid != null && mAllAppsGrid.isVisible(); } // AllAppsView.Watcher public void zoomed(float zoom) { - if (zoom == 1.0f) { + // In XLarge view, we zoom down the workspace below all apps so it's still visible + if (zoom == 1.0f && !LauncherApplication.isScreenXLarge()) { mWorkspace.setVisibility(View.GONE); } } + + private void showToolbarButton(View button) { + button.setAlpha(1.0f); + button.setVisibility(View.VISIBLE); + button.setFocusable(true); + button.setClickable(true); + } + + private void hideToolbarButton(View button) { + button.setAlpha(0.0f); + // We can't set it to GONE, otherwise the RelativeLayout gets screwed up + button.setVisibility(View.INVISIBLE); + button.setFocusable(false); + button.setClickable(false); + } + + /** + * Helper function for showing or hiding a toolbar button, possibly animated. + * + * @param show If true, create an animation to the show the item. Otherwise, hide it. + * @param view The toolbar button to be animated + * @param seq A AnimatorSet that will be used to animate the transition. If null, the + * transition will not be animated. + */ + private void hideOrShowToolbarButton(boolean show, final View view, AnimatorSet seq) { + final boolean showing = show; + final boolean hiding = !show; + + final int duration = show ? + getResources().getInteger(R.integer.config_toolbarButtonFadeInTime) : + getResources().getInteger(R.integer.config_toolbarButtonFadeOutTime); + + if (seq != null) { + Animator anim = new ObjectAnimator<Float>(duration, view, "alpha", show ? 1.0f : 0.0f); + anim.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + if (showing) showToolbarButton(view); + } + public void onAnimationEnd(Animator animation) { + if (hiding) hideToolbarButton(view); + } + }); + seq.play(anim); + } else { + if (showing) { + showToolbarButton(view); + } else { + hideToolbarButton(view); + } + } + } + + /** + * Show/hide the appropriate toolbar buttons for newState. + * If showSeq or hideSeq is null, the transition will be done immediately (not animated). + * + * @param newState The state that is being switched to + * @param showSeq AnimatorSet in which to put "show" animations, or null. + * @param hideSeq AnimatorSet in which to put "hide" animations, or null. + */ + private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) { + final View searchButton = findViewById(R.id.search_button); + final View allAppsButton = findViewById(R.id.all_apps_button); + final View marketButton = findViewById(R.id.market_button); + final View configureButton = findViewById(R.id.configure_button); + + switch (newState) { + case WORKSPACE: + hideOrShowToolbarButton(true, searchButton, showSeq); + hideOrShowToolbarButton(true, allAppsButton, showSeq); + hideOrShowToolbarButton(true, configureButton, showSeq); + hideOrShowToolbarButton(false, marketButton, hideSeq); + mDeleteZone.setHandle(allAppsButton); + break; + case ALL_APPS: + hideOrShowToolbarButton(true, configureButton, showSeq); + hideOrShowToolbarButton(true, marketButton, showSeq); + hideOrShowToolbarButton(false, searchButton, hideSeq); + hideOrShowToolbarButton(false, allAppsButton, hideSeq); + mDeleteZone.setHandle(marketButton); + break; + case CUSTOMIZE: + hideOrShowToolbarButton(true, allAppsButton, showSeq); + hideOrShowToolbarButton(false, searchButton, hideSeq); + hideOrShowToolbarButton(false, marketButton, hideSeq); + hideOrShowToolbarButton(false, configureButton, hideSeq); + mDeleteZone.setHandle(allAppsButton); + break; + } + } + + /** + * Helper method for the cameraZoomIn/cameraZoomOut animations + * @param view The view being animated + * @param state The state that we are moving in or out of -- either ALL_APPS or CUSTOMIZE + * @param scaleFactor The scale factor used for the zoom + */ + private void setPivotsForZoom(View view, State state, float scaleFactor) { + final int height = view.getHeight(); + view.setPivotX(view.getWidth() / 2.0f); + // Set pivotY so that at the starting zoom factor, the view is off-screen by a small margin + // Assumes that the view is normally anchored to either the top or bottom of the screen + final int margin = getResources().getInteger(R.integer.config_allAppsVerticalOffset); + if (state == State.ALL_APPS) { + view.setPivotY(height + ((view.getTop() + height) / scaleFactor) + margin); + } else { + view.setPivotY(0.0f - (view.getTop() / scaleFactor) - margin); + } + } + + /** + * Zoom the camera out from the workspace to reveal 'toView'. + * Assumes that the view to show is anchored at either the very top or very bottom + * of the screen. + * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE. + */ + private void cameraZoomOut(State toState, boolean animated) { + final Resources res = getResources(); + final int duration = res.getInteger(R.integer.config_allAppsZoomInTime); + final float scale = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor); + final boolean toAllApps = (toState == State.ALL_APPS); + final View toView = toAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + + setPivotsForZoom(toView, toState, scale); + + if (toState == State.ALL_APPS) { + mWorkspace.shrinkToBottom(animated); + } else { + mWorkspace.shrinkToTop(animated); + } + + if (animated) { + ValueAnimator scaleAnim = new ObjectAnimator(duration, toView, + new PropertyValuesHolder<Float>("scaleX", scale, 1.0f), + new PropertyValuesHolder<Float>("scaleY", scale, 1.0f)); + scaleAnim.setInterpolator(new DecelerateInterpolator()); + scaleAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + // Prepare the position + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setVisibility(View.VISIBLE); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim); + + // toView should appear right at the end of the workspace shrink animation + final int startDelay = res.getInteger(R.integer.config_workspaceShrinkTime) - duration; + + AnimatorSet s = new AnimatorSet(); + s.playTogether(scaleAnim, toolbarHideAnim); + s.play(scaleAnim).after(startDelay); + + // Show the new toolbar buttons just as the main animation is ending + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + s.play(toolbarShowAnim).after(duration + startDelay - fadeInTime); + s.start(); + } else { + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + toView.setVisibility(View.VISIBLE); + hideAndShowToolbarButtons(toState, null, null); + } + } + + /** + * Zoom the camera back into the workspace, hiding 'fromView'. + * This is the opposite of cameraZoomOut. + * @param fromState The current state (must be ALL_APPS or CUSTOMIZE). + * @param animated If true, the transition will be animated. + */ + private void cameraZoomIn(State fromState, boolean animated) { + Resources res = getResources(); + int duration = res.getInteger(R.integer.config_allAppsZoomOutTime); + float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor); + final View fromView = + (fromState == State.ALL_APPS) ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + + mCustomizePagedView.endChoiceMode(); + mAllAppsPagedView.endChoiceMode(); + + setPivotsForZoom(fromView, fromState, scaleFactor); + + mWorkspace.unshrink(animated); + + if (animated) { + AnimatorSet s = new AnimatorSet(); + ValueAnimator scaleAnim = new ObjectAnimator(duration, fromView, + new PropertyValuesHolder<Float>("scaleX", scaleFactor), + new PropertyValuesHolder<Float>("scaleY", scaleFactor)); + scaleAnim.setInterpolator(new AccelerateInterpolator()); + s.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + fromView.setVisibility(View.GONE); + fromView.setScaleX(1.0f); + fromView.setScaleY(1.0f); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim); + + s.playTogether(scaleAnim, toolbarHideAnim); + + // Show the new toolbar buttons at the very end of the whole animation + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + final int unshrinkTime = res.getInteger(R.integer.config_workspaceUnshrinkTime); + s.play(toolbarShowAnim).after(unshrinkTime - fadeInTime); + s.start(); + } else { + fromView.setVisibility(View.GONE); + hideAndShowToolbarButtons(State.WORKSPACE, null, null); + } + } + + /** + * Pan the camera in the vertical plane between 'fromView' and 'toView'. + * This is the transition used on xlarge screens to go between all apps and + * the home customization drawer. + * @param fromState The view to pan away from. Must be ALL_APPS or CUSTOMIZE. + * @param toState The view to pan into the frame. Must be ALL_APPS or CUSTOMIZE. + * @param animated If true, the transition will be animated. + */ + private void cameraPan(State fromState, State toState, boolean animated) { + final Resources res = getResources(); + final int duration = res.getInteger(R.integer.config_allAppsCameraPanTime); + final int workspaceHeight = mWorkspace.getHeight(); + + final boolean fromAllApps = (fromState == State.ALL_APPS); + final View fromView = fromAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + final View toView = fromAllApps ? mHomeCustomizationDrawer : (View) mAllAppsGrid; + + final float fromViewStartY = fromAllApps ? 0.0f : fromView.getY(); + final float fromViewEndY = fromAllApps ? -fromView.getHeight() * 2 : workspaceHeight * 2; + final float toViewStartY = fromAllApps ? workspaceHeight * 2 : -toView.getHeight() * 2; + final float toViewEndY = fromAllApps ? workspaceHeight - toView.getHeight() : 0.0f; + + mCustomizePagedView.endChoiceMode(); + mAllAppsPagedView.endChoiceMode(); + + if (toState == State.ALL_APPS) { + mWorkspace.shrinkToBottom(animated); + } else { + mWorkspace.shrinkToTop(animated); + } + + if (animated) { + AnimatorSet s = new AnimatorSet(); + s.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + toView.setVisibility(View.VISIBLE); + toView.setY(toViewStartY); + } + public void onAnimationEnd(Animator animation) { + fromView.setVisibility(View.GONE); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim); + + s.playTogether( + toolbarHideAnim, + new ObjectAnimator(duration, fromView, "y", fromViewStartY, fromViewEndY), + new ObjectAnimator(duration, toView, "y", toViewStartY, toViewEndY)); + + // Show the new toolbar buttons just as the main animation is ending + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + s.play(toolbarShowAnim).after(duration - fadeInTime); + s.start(); + } else { + fromView.setY(fromViewEndY); + fromView.setVisibility(View.GONE); + toView.setY(toViewEndY); + toView.setVisibility(View.VISIBLE); + hideAndShowToolbarButtons(toState, null, null); + } + } void showAllApps(boolean animated) { - mAllAppsGrid.zoom(1.0f, animated); + if (mAllAppsGrid.isVisible()) + return; + + if (LauncherApplication.isScreenXLarge()) { + if (isCustomizationDrawerVisible()) { + cameraPan(State.CUSTOMIZE, State.ALL_APPS, animated); + } else { + cameraZoomOut(State.ALL_APPS, animated); + } + } else { + mAllAppsGrid.zoom(1.0f, animated); + } ((View) mAllAppsGrid).setFocusable(true); ((View) mAllAppsGrid).requestFocus(); - + // TODO: fade these two too mDeleteZone.setVisibility(View.GONE); } @@ -1962,9 +2563,13 @@ public final class Launcher extends Activity void closeAllApps(boolean animated) { if (mAllAppsGrid.isVisible()) { mWorkspace.setVisibility(View.VISIBLE); - mAllAppsGrid.zoom(0.0f, animated); + if (LauncherApplication.isScreenXLarge()) { + cameraZoomIn(State.ALL_APPS, animated); + } else { + mAllAppsGrid.zoom(0.0f, animated); + } ((View)mAllAppsGrid).setFocusable(false); - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } } @@ -1976,6 +2581,92 @@ public final class Launcher extends Activity // TODO } + private boolean isCustomizationDrawerVisible() { + return mHomeCustomizationDrawer != null && + mHomeCustomizationDrawer.getVisibility() == View.VISIBLE; + } + + // Show the customization drawer (only exists in x-large configuration) + private void showCustomizationDrawer(boolean animated) { + if (isAllAppsVisible()) { + cameraPan(State.ALL_APPS, State.CUSTOMIZE, animated); + } else { + cameraZoomOut(State.CUSTOMIZE, animated); + } + } + + // Hide the customization drawer (only exists in x-large configuration) + void hideCustomizationDrawer(boolean animated) { + if (isCustomizationDrawerVisible()) { + cameraZoomIn(State.CUSTOMIZE, animated); + } + } + + void onWorkspaceClick(CellLayout layout) { + Object itemInfo = mAllAppsPagedView.getChosenItem(); + if (itemInfo == null) { + itemInfo = mCustomizePagedView.getChosenItem(); + } + + if (itemInfo == null) { + // No items are chosen in All Apps or Customize, so just zoom into the workspace + mWorkspace.unshrink(layout); + if (isAllAppsVisible()) { + closeAllApps(true); + } else if (isCustomizationDrawerVisible()) { + hideCustomizationDrawer(true); + } + } else { + // Act as if the chosen item was dropped on the given CellLayout + if (mWorkspace.addExternalItemToScreen(itemInfo, layout)) { + mAllAppsPagedView.endChoiceMode(); + mCustomizePagedView.endChoiceMode(); + } else { + Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); + } + } + } + + /** + * Sets the app market icon (shown when all apps is visible on x-large screens) + */ + private void updateAppMarketIcon() { + if (LauncherApplication.isScreenXLarge()) { + // Find the app market activity by resolving an intent. + // (If multiple app markets are installed, it will return the ResolverActivity.) + PackageManager packageManager = getPackageManager(); + Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET); + ComponentName activityName = intent.resolveActivity(getPackageManager()); + if (activityName != null) { + mAppMarketIntent = intent; + ImageView marketButton = (ImageView) findViewById(R.id.market_button); + Drawable toolbarIcon = null; + try { + // Look for the toolbar icon specified in the activity meta-data + Bundle metaData = packageManager.getActivityInfo( + activityName, PackageManager.GET_META_DATA).metaData; + if (metaData != null) { + int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForActivity(activityName); + toolbarIcon = res.getDrawable(iconResId); + } + } + } catch (NameNotFoundException e) { + // Do nothing + } + // If we were unable to find the icon via the meta-data, use a generic one + if (toolbarIcon == null) { + marketButton.setImageResource(R.drawable.app_market_generic); + } else { + marketButton.setImageDrawable(toolbarIcon); + } + } else { + mAppMarketIntent = null; + } + } + } + /** * Displays the shortcut creation dialog and launches, if necessary, the * appropriate activity. @@ -2028,7 +2719,6 @@ public final class Launcher extends Activity switch (which) { case AddAdapter.ITEM_SHORTCUT: { - // Insert extra item to handle picking application pickShortcut(); break; } @@ -2076,7 +2766,7 @@ public final class Launcher extends Activity } public void onShow(DialogInterface dialog) { - mWaitingForResult = true; + mWaitingForResult = true; } } @@ -2094,6 +2784,7 @@ public final class Launcher extends Activity animate = false; } closeAllApps(animate); + hideCustomizationDrawer(animate); } } } @@ -2117,12 +2808,16 @@ public final class Launcher extends Activity */ public int getCurrentWorkspaceScreen() { if (mWorkspace != null) { - return mWorkspace.getCurrentScreen(); + return mWorkspace.getCurrentPage(); } else { return SCREEN_COUNT / 2; } } + void setAllAppsPagedView(PagedView view) { + mAllAppsPagedView = view; + } + /** * Refreshes the shortcuts shown on the workspace. * @@ -2170,7 +2865,7 @@ public final class Launcher extends Activity break; case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), + (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (UserFolderInfo) item); workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1, false); @@ -2178,7 +2873,7 @@ public final class Launcher extends Activity case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: final FolderIcon newLiveFolder = LiveFolderIcon.fromXml( R.layout.live_folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), + (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (LiveFolderInfo) item); workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, false); @@ -2241,7 +2936,7 @@ public final class Launcher extends Activity public void finishBindingItems() { if (mSavedState != null) { if (!mWorkspace.hasFocus()) { - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); @@ -2276,6 +2971,7 @@ public final class Launcher extends Activity */ public void bindAllApplications(ArrayList<ApplicationInfo> apps) { mAllAppsGrid.setApps(apps); + updateAppMarketIcon(); } /** @@ -2286,6 +2982,7 @@ public final class Launcher extends Activity public void bindAppsAdded(ArrayList<ApplicationInfo> apps) { removeDialog(DIALOG_CREATE_SHORTCUT); mAllAppsGrid.addApps(apps); + updateAppMarketIcon(); } /** @@ -2297,6 +2994,7 @@ public final class Launcher extends Activity removeDialog(DIALOG_CREATE_SHORTCUT); mWorkspace.updateShortcuts(apps); mAllAppsGrid.updateApps(apps); + updateAppMarketIcon(); } /** @@ -2310,6 +3008,16 @@ public final class Launcher extends Activity mWorkspace.removeItems(apps); } mAllAppsGrid.removeApps(apps); + updateAppMarketIcon(); + } + + /** + * A number of packages were updated. + */ + public void bindPackagesUpdated() { + // update the customization drawer contents + if (mCustomizePagedView != null) + mCustomizePagedView.update(); } /** |