summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/drawable/search_button_bg.9.pngbin0 -> 676 bytes
-rw-r--r--res/drawable/search_button_voice.pngbin0 -> 965 bytes
-rw-r--r--res/drawable/search_floater.9.pngbin0 -> 777 bytes
-rw-r--r--res/drawable/textfield_searchwidget.xml31
-rw-r--r--res/drawable/textfield_searchwidget_default.9.pngbin0 -> 1742 bytes
-rw-r--r--res/drawable/textfield_searchwidget_pressed.9.pngbin0 -> 2032 bytes
-rw-r--r--res/drawable/textfield_searchwidget_selected.9.pngbin0 -> 1886 bytes
-rw-r--r--res/layout/widget_search.xml79
-rw-r--r--res/xml/default_workspace.xml2
-rw-r--r--src/com/android/launcher/Launcher.java218
-rw-r--r--src/com/android/launcher/Search.java823
-rw-r--r--src/com/android/launcher/Workspace.java102
12 files changed, 431 insertions, 824 deletions
diff --git a/res/drawable/search_button_bg.9.png b/res/drawable/search_button_bg.9.png
new file mode 100644
index 000000000..b140b24b6
--- /dev/null
+++ b/res/drawable/search_button_bg.9.png
Binary files differ
diff --git a/res/drawable/search_button_voice.png b/res/drawable/search_button_voice.png
new file mode 100644
index 000000000..4c3d683e7
--- /dev/null
+++ b/res/drawable/search_button_voice.png
Binary files differ
diff --git a/res/drawable/search_floater.9.png b/res/drawable/search_floater.9.png
new file mode 100644
index 000000000..a2007ad92
--- /dev/null
+++ b/res/drawable/search_floater.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget.xml b/res/drawable/textfield_searchwidget.xml
new file mode 100644
index 000000000..80f3dca34
--- /dev/null
+++ b/res/drawable/textfield_searchwidget.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/textfield_searchwidget_default" />
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/textfield_searchwidget_pressed" />
+
+ <item android:state_enabled="true" android:state_focused="true"
+ android:drawable="@drawable/textfield_searchwidget_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/textfield_searchwidget_default" />
+
+</selector>
diff --git a/res/drawable/textfield_searchwidget_default.9.png b/res/drawable/textfield_searchwidget_default.9.png
new file mode 100644
index 000000000..247abca85
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_default.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_pressed.9.png b/res/drawable/textfield_searchwidget_pressed.9.png
new file mode 100644
index 000000000..d628cbca1
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_pressed.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_selected.9.png b/res/drawable/textfield_searchwidget_selected.9.png
new file mode 100644
index 000000000..9e935273e
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_selected.9.png
Binary files differ
diff --git a/res/layout/widget_search.xml b/res/layout/widget_search.xml
index 1db8488a9..841b1550d 100644
--- a/res/layout/widget_search.xml
+++ b/res/layout/widget_search.xml
@@ -14,52 +14,47 @@
limitations under the License.
-->
-<com.android.launcher.Search xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher.Search
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+ android:id="@+id/widget_search"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal"
- android:background="@drawable/search_bg"
- android:gravity="center_vertical"
- >
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="top">
- <ImageView
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:id="@+id/search_plate"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:paddingRight="3dip"
- android:src="@drawable/google_logo" />
-
- <com.android.launcher.SearchAutoCompleteTextView
- android:id="@+id/input"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_marginTop="1dip"
- android:hint="@string/search_hint"
- android:focusableInTouchMode="false"
- android:singleLine="true"
- android:selectAllOnFocus="true"
- android:completionThreshold="1"
- android:inputType="textAutoComplete"
- android:imeOptions="actionSearch"
- android:lines="1"
- android:dropDownWidth="fill_parent"
- android:popupBackground="@drawable/spinner_dropdown_background"
+ android:orientation="horizontal"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="7dip"
+ android:paddingBottom="13dip"
+ android:background="@drawable/search_floater" >
+
+ <TextView
+ android:id="@+id/search_src_text"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:editable="false"
+ android:inputType="text"
+ android:background="@drawable/textfield_searchwidget"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:textColor="@android:color/primary_text_light"
/>
- <ImageButton android:id="@+id/search_go_btn"
- android:layout_marginLeft="5dip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@*android:drawable/ic_btn_search"
- style="@style/SearchButton"
- />
-
- <ImageButton android:id="@+id/search_voice_btn"
- android:layout_marginLeft="4dip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@android:drawable/ic_btn_speak_now"
- style="@style/SearchButton"
- />
+ <ImageButton
+ android:id="@+id/search_voice_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_marginLeft="8dip"
+ android:background="@*android:drawable/btn_search_dialog_voice"
+ android:src="@*android:drawable/ic_btn_speak_now"
+ />
+
+ </LinearLayout>
</com.android.launcher.Search>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
index 771bb2991..cc5f91158 100644
--- a/res/xml/default_workspace.xml
+++ b/res/xml/default_workspace.xml
@@ -19,7 +19,7 @@
<clock
launcher:screen="2"
launcher:x="1"
- launcher:y="0" />
+ launcher:y="1" />
<search
launcher:screen="1"
diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java
index 7dd341819..3bf96aae8 100644
--- a/src/com/android/launcher/Launcher.java
+++ b/src/com/android/launcher/Launcher.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.app.Dialog;
+import android.app.IWallpaperService;
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.content.ActivityNotFoundException;
@@ -35,26 +36,24 @@ import android.content.Intent.ShortcutIconResource;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Message;
import android.os.MessageQueue;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.Message;
-import android.provider.*;
-import android.telephony.PhoneNumberUtils;
+import android.provider.LiveFolders;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -71,11 +70,11 @@ import android.view.ViewGroup;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
import android.widget.GridView;
+import android.widget.ListView;
import android.widget.SlidingDrawer;
-import android.app.IWallpaperService;
+import android.widget.TextView;
+import android.widget.Toast;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -168,7 +167,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
private final ContentObserver mObserver = new FavoritesChangeObserver();
- private final ContentObserver mAppWidgetResetObserver = new AppWidgetResetObserver();
private LayoutInflater mInflater;
@@ -353,6 +351,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On
if (mRestoring) {
startLoaders();
}
+
+ // Make sure that the search gadget (if any) is in its normal place.
+ stopSearch(false);
}
@Override
@@ -387,45 +388,29 @@ public final class Launcher extends Activity implements View.OnClickListener, On
boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
keyCode, event);
if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
- // something usable has been typed - dispatch it now.
- final String str = mDefaultKeySsb.toString();
-
- boolean isDialable = true;
- final int count = str.length();
- for (int i = 0; i < count; i++) {
- if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
- isDialable = false;
- break;
- }
- }
- Intent intent;
- if (isDialable) {
- intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
- } else {
- intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
- intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
- }
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
- try {
- startActivity(intent);
- } catch (android.content.ActivityNotFoundException ex) {
- // Oh well... no one knows how to filter/dial. Life goes on.
- }
-
- mDefaultKeySsb.clear();
- mDefaultKeySsb.clearSpans();
- Selection.setSelection(mDefaultKeySsb, 0);
-
- return true;
+ // something usable has been typed - start a search
+ // the typed text will be retrieved and cleared by
+ // showSearchDialog()
+ // If there are multiple keystrokes before the search dialog takes focus,
+ // onSearchRequested() will be called for every keystroke,
+ // but it is idempotent, so it's fine.
+ return onSearchRequested();
}
}
return handled;
}
+ private String getTypedText() {
+ return mDefaultKeySsb.toString();
+ }
+
+ private void clearTypedText() {
+ mDefaultKeySsb.clear();
+ mDefaultKeySsb.clearSpans();
+ Selection.setSelection(mDefaultKeySsb, 0);
+ }
+
/**
* Restores the previous state, if it exists.
*
@@ -495,7 +480,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On
drawer.setOnDrawerCloseListener(drawerManager);
drawer.setOnDrawerScrollListener(drawerManager);
- grid.setTextFilterEnabled(true);
+ grid.setTextFilterEnabled(false);
grid.setDragger(dragLayer);
grid.setLauncher(this);
@@ -827,7 +812,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
sModel.abortLoaders();
getContentResolver().unregisterContentObserver(mObserver);
- getContentResolver().unregisterContentObserver(mAppWidgetResetObserver);
unregisterReceiver(mApplicationsReceiver);
}
@@ -840,13 +824,76 @@ public final class Launcher extends Activity implements View.OnClickListener, On
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
+
+ closeDrawer(false);
+
+ // Slide the search widget to the top, if it's on the current screen,
+ // otherwise show the search dialog immediately.
+ Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget == null) {
+ showSearchDialog(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ searchWidget.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ // show the currently typed text in the search widget while sliding
+ searchWidget.setQuery(getTypedText());
+ }
+ }
+
+ /**
+ * Show the search dialog immediately, without changing the search widget.
+ * See {@link Activity.startSearch()} for the arguments.
+ */
+ public void showSearchDialog(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+
+ if (initialQuery == null) {
+ // Use any text typed in the launcher as the initial query
+ initialQuery = getTypedText();
+ clearTypedText();
+ }
if (appSearchData == null) {
appSearchData = new Bundle();
appSearchData.putString(SearchManager.SOURCE, "launcher-search");
}
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+
+ final Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget != null) {
+ // This gets called when the user leaves the search dialog to go back to
+ // the Launcher.
+ searchManager.setOnCancelListener(new SearchManager.OnCancelListener() {
+ public void onCancel() {
+ searchManager.setOnCancelListener(null);
+ stopSearch(true);
+ }
+ });
+ }
+
+ searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ appSearchData, globalSearch);
}
+ /**
+ * Cancel search dialog if it is open.
+ *
+ * @param animate Whether to animate the search gadget (if any) when restoring it
+ * to its original position.
+ */
+ public void stopSearch(boolean animate) {
+ // Close search dialog
+ SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ if (searchManager.isVisible()) {
+ searchManager.stopSearch();
+ }
+ // Restore search widget to its normal position
+ Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget != null) {
+ searchWidget.stopSearch(false);
+ }
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mDesktopLocked) return false;
@@ -905,15 +952,16 @@ public final class Launcher extends Activity implements View.OnClickListener, On
return super.onOptionsItemSelected(item);
}
+
+ /**
+ * Indicates that we want global search for this activity by setting the globalSearch
+ * argument for {@link #startSearch} to true.
+ */
@Override
public boolean onSearchRequested() {
- if (mWorkspace.snapToSearch()) {
- closeDrawer(true); // search widget: get drawer out of the way
- return true;
- } else {
- return super.onSearchRequested(); // no search widget: use system search UI
- }
+ startSearch(null, false, null, true);
+ return true;
}
private void addItems() {
@@ -975,6 +1023,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On
final View view = mInflater.inflate(info.layoutResource, null);
view.setTag(info);
+ Search search = (Search) view.findViewById(R.id.widget_search);
+ search.setLauncher(this);
mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
}
@@ -1158,7 +1208,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
private void registerContentObservers() {
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver);
- resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, true, mAppWidgetResetObserver);
}
@Override
@@ -1224,16 +1273,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
sModel.loadUserItems(false, this, false, false);
}
- /**
- * When reset, we handle by calling {@link AppWidgetHost#startListening()}
- * to make sure our callbacks are set correctly.
- */
- private void onAppWidgetReset() {
- if (mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
- }
- }
-
void onDesktopItemsLoaded() {
if (mDestroyed) return;
bindDesktopItems();
@@ -1250,8 +1289,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
return;
}
- mAllAppsGrid.setAdapter(drawerAdapter);
-
final Workspace workspace = mWorkspace;
int count = workspace.getChildCount();
for (int i = 0; i < count; i++) {
@@ -1275,7 +1312,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On
mBinder.mTerminate = true;
}
- mBinder = new DesktopBinder(this, shortcuts, appWidgets);
+ mBinder = new DesktopBinder(this, shortcuts, appWidgets, drawerAdapter);
mBinder.startBindingItems();
}
@@ -1317,6 +1354,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On
final View view = mInflater.inflate(R.layout.widget_search,
(ViewGroup) workspace.getChildAt(screen), false);
+ Search search = (Search) view.findViewById(R.id.widget_search);
+ search.setLauncher(this);
+
final Widget widget = (Widget) item;
view.setTag(widget);
@@ -1329,7 +1369,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On
if (end >= count) {
finishBindDesktopItems();
- binder.startBindingAppWidgetsWhenIdle();
+ binder.startBindingDrawer();
} else {
binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget();
}
@@ -1376,6 +1416,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On
mDrawer.unlock();
}
+ private void bindDrawer(Launcher.DesktopBinder binder,
+ ApplicationsAdapter drawerAdapter) {
+ mAllAppsGrid.setAdapter(drawerAdapter);
+ binder.startBindingAppWidgetsWhenIdle();
+ }
+
private void bindAppWidgets(Launcher.DesktopBinder binder,
LinkedList<LauncherAppWidgetInfo> appWidgets) {
@@ -1386,14 +1432,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On
final LauncherAppWidgetInfo item = appWidgets.removeFirst();
final int appWidgetId = item.appWidgetId;
- final AppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
- if (LOGD) {
- d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s",
- appWidgetId, appWidgetInfo));
- }
+ if (LOGD) d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo));
item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
item.hostView.setTag(item);
@@ -1891,21 +1933,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On
}
/**
- * Receives notifications when the {@link AppWidgetHost} has been reset,
- * usually only when the {@link LauncherProvider} database is first created.
- */
- private class AppWidgetResetObserver extends ContentObserver {
- public AppWidgetResetObserver() {
- super(new Handler());
- }
-
- @Override
- public void onChange(boolean selfChange) {
- onAppWidgetReset();
- }
- }
-
- /**
* Receives intents from other applications to change the wallpaper.
*/
private static class WallpaperIntentReceiver extends BroadcastReceiver {
@@ -1996,21 +2023,25 @@ public final class Launcher extends Activity implements View.OnClickListener, On
private static class DesktopBinder extends Handler implements MessageQueue.IdleHandler {
static final int MESSAGE_BIND_ITEMS = 0x1;
static final int MESSAGE_BIND_APPWIDGETS = 0x2;
+ static final int MESSAGE_BIND_DRAWER = 0x3;
// Number of items to bind in every pass
static final int ITEMS_COUNT = 6;
private final ArrayList<ItemInfo> mShortcuts;
private final LinkedList<LauncherAppWidgetInfo> mAppWidgets;
+ private final ApplicationsAdapter mDrawerAdapter;
private final WeakReference<Launcher> mLauncher;
- public volatile boolean mTerminate = false;
+ public boolean mTerminate = false;
DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts,
- ArrayList<LauncherAppWidgetInfo> appWidgets) {
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ApplicationsAdapter drawerAdapter) {
mLauncher = new WeakReference<Launcher>(launcher);
mShortcuts = shortcuts;
+ mDrawerAdapter = drawerAdapter;
// Sort widgets so active workspace is bound first
final int currentScreen = launcher.mWorkspace.getCurrentScreen();
@@ -2030,6 +2061,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On
public void startBindingItems() {
obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget();
}
+
+ public void startBindingDrawer() {
+ obtainMessage(MESSAGE_BIND_DRAWER).sendToTarget();
+ }
public void startBindingAppWidgetsWhenIdle() {
// Ask for notification when message queue becomes idle
@@ -2059,6 +2094,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On
launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2);
break;
}
+ case MESSAGE_BIND_DRAWER: {
+ launcher.bindDrawer(this, mDrawerAdapter);
+ break;
+ }
case MESSAGE_BIND_APPWIDGETS: {
launcher.bindAppWidgets(this, mAppWidgets);
break;
@@ -2067,3 +2106,4 @@ public final class Launcher extends Activity implements View.OnClickListener, On
}
}
}
+
diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java
index 71ab7ef44..3d0947987 100644
--- a/src/com/android/launcher/Search.java
+++ b/src/com/android/launcher/Search.java
@@ -16,71 +16,59 @@
package com.android.launcher;
-import android.app.ISearchManager;
-import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
-import android.widget.AdapterView;
-import android.widget.AutoCompleteTextView;
-import android.widget.CursorAdapter;
-import android.widget.Filter;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.inputmethod.InputMethodManager;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
-public class Search extends LinearLayout implements OnClickListener, OnKeyListener,
- OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener {
+public class Search extends LinearLayout
+ implements OnClickListener, OnKeyListener, OnLongClickListener {
+
+ // Speed at which the widget slides up/down, in pixels/ms.
+ private static final float ANIMATION_VELOCITY = 1.0f;
private final String TAG = "SearchWidget";
- private AutoCompleteTextView mSearchText;
- private ImageButton mGoButton;
+ private Launcher mLauncher;
+
+ private TextView mSearchText;
private ImageButton mVoiceButton;
- private OnLongClickListener mLongClickListener;
-
- // Support for suggestions
- private SuggestionsAdapter mSuggestionsAdapter;
- private SearchableInfo mSearchable;
- private String mSuggestionAction = null;
- private Uri mSuggestionData = null;
- private String mSuggestionQuery = null;
- private int mItemSelected = -1;
-
+
+ /** The animation that morphs the search widget to the search dialog. */
+ private Animation mMorphAnimation;
+
+ /** The animation that morphs the search widget back to its normal position. */
+ private Animation mUnmorphAnimation;
+
+ // These four are passed to Launcher.startSearch() when the search widget
+ // has finished morphing. They are instance variables to make it possible to update
+ // them while the widget is morphing.
+ private String mInitialQuery;
+ private boolean mSelectInitialQuery;
+ private Bundle mAppSearchData;
+ private boolean mGlobalSearch;
+
// For voice searching
private Intent mVoiceSearchIntent;
- private Rect mTempRect = new Rect();
- private boolean mRestoreFocus = false;
-
/**
* Used to inflate the Workspace from XML.
*
@@ -89,293 +77,235 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen
*/
public Search(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+
+ mMorphAnimation = new ToParentOriginAnimation();
+ // no need to apply transformation before the animation starts,
+ // since the gadget is already in its normal place.
+ mMorphAnimation.setFillBefore(false);
+ // stay in the top position after the animation finishes
+ mMorphAnimation.setFillAfter(true);
+ mMorphAnimation.setInterpolator(interpolator);
+ mMorphAnimation.setAnimationListener(new Animation.AnimationListener() {
+ // The amount of time before the animation ends to show the search dialog.
+ private static final long TIME_BEFORE_ANIMATION_END = 80;
+
+ // The runnable which we'll pass to our handler to show the search dialog.
+ private final Runnable mShowSearchDialogRunnable = new Runnable() {
+ public void run() {
+ showSearchDialog();
+ }
+ };
+
+ public void onAnimationEnd(Animation animation) { }
+ public void onAnimationRepeat(Animation animation) { }
+ public void onAnimationStart(Animation animation) {
+ // Make the search dialog show up ideally *just* as the animation reaches
+ // the top, to aid the illusion that the widget becomes the search dialog.
+ // Otherwise, there is a short delay when the widget reaches the top before
+ // the search dialog shows. We do this roughly 80ms before the animation ends.
+ getHandler().postDelayed(
+ mShowSearchDialogRunnable,
+ Math.max(mMorphAnimation.getDuration() - TIME_BEFORE_ANIMATION_END, 0));
+ }
+ });
+
+ mUnmorphAnimation = new FromParentOriginAnimation();
+ // stay in the top position until the animation starts
+ mUnmorphAnimation.setFillBefore(true);
+ // no need to apply transformation after the animation finishes,
+ // since the gadget is now back in its normal place.
+ mUnmorphAnimation.setFillAfter(false);
+ mUnmorphAnimation.setInterpolator(interpolator);
+ mUnmorphAnimation.setAnimationListener(new Animation.AnimationListener(){
+ public void onAnimationEnd(Animation animation) {
+ clearAnimation();
+ }
+ public void onAnimationRepeat(Animation animation) { }
+ public void onAnimationStart(Animation animation) { }
+ });
mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
}
-
+
/**
- * Implements OnClickListener (for button)
+ * Implements OnClickListener.
*/
public void onClick(View v) {
- if (v == mGoButton) {
- query();
- } else if (v == mVoiceButton) {
- try {
- getContext().startActivity(mVoiceSearchIntent);
- } catch (ActivityNotFoundException ex) {
- // Should not happen, since we check the availability of
- // voice search before showing the button. But just in case...
- Log.w(TAG, "Could not find voice search activity");
- }
+ if (v == mVoiceButton) {
+ startVoiceSearch();
+ } else {
+ mLauncher.onSearchRequested();
}
}
- private void query() {
- String query = mSearchText.getText().toString();
- if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
- return;
+ private void startVoiceSearch() {
+ try {
+ getContext().startActivity(mVoiceSearchIntent);
+ } catch (ActivityNotFoundException ex) {
+ // Should not happen, since we check the availability of
+ // voice search before showing the button. But just in case...
+ Log.w(TAG, "Could not find voice search activity");
}
- Bundle appData = new Bundle();
- appData.putString(SearchManager.SOURCE, "launcher-widget");
- sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
- clearQuery();
}
-
+
/**
- * Assemble a search intent and send it.
- *
- * This is copied from SearchDialog.
- *
- * @param action The intent to send, typically Intent.ACTION_SEARCH
- * @param data The data for the intent
- * @param query The user text entered (so far)
- * @param appData The app data bundle (if supplied)
- * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
- * be sent here. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
- * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
- * corresponding tag message will be sent here. Pass null for no actionKey message.
- * @param si Reference to the current SearchableInfo. Passed here so it can be used even after
- * we've called dismiss(), which attempts to null mSearchable.
+ * Sets the query text. The query field is not editable, instead we forward
+ * the key events to the launcher, which keeps track of the text,
+ * calls setQuery() to show it, and gives it to the search dialog.
*/
- private void sendLaunchIntent(final String action, final Uri data, final String query,
- final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
- Intent launcher = new Intent(action);
- launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (query != null) {
- launcher.putExtra(SearchManager.QUERY, query);
- }
-
- if (data != null) {
- launcher.setData(data);
- }
-
- if (appData != null) {
- launcher.putExtra(SearchManager.APP_DATA, appData);
- }
-
- // add launch info (action key, etc.)
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
- launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
- }
-
- // attempt to enforce security requirement (no 3rd-party intents)
- if (si != null) {
- launcher.setComponent(si.mSearchActivity);
- }
-
- getContext().startActivity(launcher);
+ public void setQuery(String query) {
+ mSearchText.setText(query, TextView.BufferType.NORMAL);
}
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- if (!hasWindowFocus && hasFocus()) {
- mRestoreFocus = true;
+ /**
+ * Morph the search gadget to the search dialog.
+ * See {@link Activity.startSearch()} for the arguments.
+ */
+ public void startSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+ mInitialQuery = initialQuery;
+ mSelectInitialQuery = selectInitialQuery;
+ mAppSearchData = appSearchData;
+ mGlobalSearch = globalSearch;
+
+ // Call up the keyboard before we actually call the search dialog so that it
+ // (hopefully) animates in at about the same time as the widget animation, and
+ // so that it becomes available as soon as possible. Only do this if a hard
+ // keyboard is not currently available.
+ if (getContext().getResources().getConfiguration().hardKeyboardHidden ==
+ Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // Make sure the text field is not focusable, so it's not responsible for
+ // causing the whole view to shift up to accommodate the keyboard.
+ mSearchText.setFocusable(false);
+
+ InputMethodManager inputManager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.showSoftInputUnchecked(0, null);
}
-
- super.onWindowFocusChanged(hasWindowFocus);
-
- if (hasWindowFocus && mRestoreFocus) {
- if (isInTouchMode()) {
- final AutoCompleteTextView searchText = mSearchText;
- searchText.setSelectAllOnFocus(false);
- searchText.requestFocusFromTouch();
- searchText.setSelectAllOnFocus(true);
+
+ if (isAtTop()) {
+ showSearchDialog();
+ } else {
+ // Start the animation, unless it has already started.
+ if (getAnimation() != mMorphAnimation) {
+ mMorphAnimation.setDuration(getAnimationDuration());
+ startAnimation(mMorphAnimation);
}
- mRestoreFocus = false;
}
}
/**
- * Implements TextWatcher (for EditText)
+ * Shows the system search dialog immediately, without any animation.
*/
- public void beforeTextChanged(CharSequence s, int start, int before, int after) {
+ private void showSearchDialog() {
+ mLauncher.showSearchDialog(
+ mInitialQuery, mSelectInitialQuery, mAppSearchData, mGlobalSearch);
}
/**
- * Implements TextWatcher (for EditText)
+ * Restore the search gadget to its normal position.
+ *
+ * @param animate Whether to animate the movement of the gadget.
*/
- public void onTextChanged(CharSequence s, int start, int before, int after) {
- // enable the button if we have one or more non-space characters
- boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
- mGoButton.setEnabled(enabled);
- mGoButton.setFocusable(enabled);
+ public void stopSearch(boolean animate) {
+ setQuery("");
+
+ // Set the search field back to focusable after making it unfocusable in
+ // startSearch, so that the home screen doesn't try to shift around when the
+ // keyboard comes up.
+ mSearchText.setFocusable(true);
+ // Only restore if we are not already restored.
+ if (getAnimation() == mMorphAnimation) {
+ if (animate && !isAtTop()) {
+ mUnmorphAnimation.setDuration(getAnimationDuration());
+ startAnimation(mUnmorphAnimation);
+ } else {
+ clearAnimation();
+ }
+ }
}
- /**
- * Implements TextWatcher (for EditText)
- */
- public void afterTextChanged(Editable s) {
+ private boolean isAtTop() {
+ return getTop() == 0;
+ }
+
+ private int getAnimationDuration() {
+ return (int) (getTop() / ANIMATION_VELOCITY);
}
/**
- * Implements OnKeyListener (for EditText and for button)
- *
- * This plays some games with state in order to "soften" the strength of suggestions
- * presented. Suggestions should not be used unless the user specifically navigates to them
- * (or clicks them, in which case it's obvious). This is not the way that AutoCompleteTextBox
- * normally works.
+ * Modify clearAnimation() to invalidate the parent. This works around
+ * an issue where the region where the end of the animation placed the view
+ * was not redrawn after clearing the animation.
*/
- public final boolean onKey(View v, int keyCode, KeyEvent event) {
- if (v == mSearchText) {
- boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER ||
- keyCode == KeyEvent.KEYCODE_SEARCH ||
- keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
- if (event.getAction() == KeyEvent.ACTION_UP) {
-// Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
- if (!mSearchText.isPopupShowing()) {
- if (searchTrigger) {
- query();
- return true;
- }
- }
+ @Override
+ public void clearAnimation() {
+ Animation animation = getAnimation();
+ if (animation != null) {
+ super.clearAnimation();
+ if (animation.hasEnded()
+ && animation.getFillAfter()
+ && animation.willChangeBounds()) {
+ ((View) getParent()).invalidate();
} else {
-// Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
-// " mItemSelected="+ mItemSelected);
- if (searchTrigger && mItemSelected < 0) {
- query();
- return true;
- }
+ invalidate();
}
- } else if (v == mGoButton || v == mVoiceButton) {
- boolean handled = false;
- if (!event.isSystem() &&
- (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
- if (mSearchText.requestFocus()) {
- handled = mSearchText.dispatchKeyEvent(event);
- }
- }
- return handled;
}
-
- return false;
- }
-
- @Override
- public void setOnLongClickListener(OnLongClickListener l) {
- super.setOnLongClickListener(l);
- mLongClickListener = l;
}
- /**
- * Implements OnLongClickListener (for button)
- */
- public boolean onLongClick(View v) {
- // Pretend that a long press on a child view is a long press on the search widget
- if (mLongClickListener != null) {
- return mLongClickListener.onLongClick(this);
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (!event.isSystem() &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
+ // Forward key events to Launcher, which will forward text
+ // to search dialog
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ return mLauncher.onKeyDown(keyCode, event);
+ case KeyEvent.ACTION_MULTIPLE:
+ return mLauncher.onKeyMultiple(keyCode, event.getRepeatCount(), event);
+ case KeyEvent.ACTION_UP:
+ return mLauncher.onKeyUp(keyCode, event);
+ }
}
return false;
}
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // Request focus unless the user tapped on the voice search button
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
- final Rect frame = mTempRect;
- mVoiceButton.getHitRect(frame);
- if (!frame.contains(x, y)) {
- requestFocusFromTouch();
- }
- return super.onInterceptTouchEvent(ev);
- }
-
/**
- * In order to keep things simple, the external trigger will clear the query just before
- * focusing, so as to give you a fresh query. This way we eliminate any sources of
- * accidental query launching.
+ * Implements OnLongClickListener to pass long clicks on child views
+ * to the widget. This makes it possible to pick up the widget by long
+ * clicking on the text field or a button.
*/
- public void clearQuery() {
- mSearchText.setText(null);
+ public boolean onLongClick(View v) {
+ return performLongClick();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
- // TODO: This can be confusing when the user taps the text field to give the focus
- // (it is not necessary but I ran into this issue several times myself)
- // mTitleInput.setOnClickListener(this);
+ mSearchText = (TextView) findViewById(R.id.search_src_text);
+ mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
+
mSearchText.setOnKeyListener(this);
- mSearchText.addTextChangedListener(this);
- mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
- mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
- mGoButton.setOnClickListener(this);
+ mSearchText.setOnClickListener(this);
mVoiceButton.setOnClickListener(this);
- mGoButton.setOnKeyListener(this);
- mVoiceButton.setOnKeyListener(this);
-
+ setOnClickListener(this);
+
mSearchText.setOnLongClickListener(this);
- mGoButton.setOnLongClickListener(this);
mVoiceButton.setOnLongClickListener(this);
-
- // disable the button since we start out w/empty input
- mGoButton.setEnabled(false);
- mGoButton.setFocusable(false);
-
- configureSearchableInfo();
- configureSuggestions();
+
configureVoiceSearchButton();
}
-
- /**
- * Cache of popup padding value after read from {@link Resources}.
- */
- private static float mPaddingInset = -1;
-
- /**
- * When our size is changed, pass down adjusted width and offset values to
- * correctly center the {@link AutoCompleteTextView} popup and include our
- * padding.
- */
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- if (mPaddingInset == -1) {
- mPaddingInset = getResources().getDimension(R.dimen.search_widget_inset);
- }
-
- // Fill entire width of widget, minus padding inset
- float paddedWidth = getWidth() - (mPaddingInset * 2);
- float paddedOffset = -(mSearchText.getLeft() - mPaddingInset);
-
- mSearchText.setDropDownWidth((int) paddedWidth);
- mSearchText.setDropDownHorizontalOffset((int) paddedOffset);
- }
- }
-
- /**
- * Read the searchable info from the search manager
- */
- private void configureSearchableInfo() {
- ISearchManager sms;
- SearchableInfo searchable;
- sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
- try {
- // TODO null isn't the published use of this API, but it works when global=true
- // TODO better implementation: defer all of this, let Home set it up
- searchable = sms.getSearchableInfo(null, true);
- } catch (RemoteException e) {
- searchable = null;
- }
- if (searchable == null) {
- // no suggestions so just get out (no need to continue)
- return;
- }
- mSearchable = searchable;
- }
-
+
/**
* If appropriate & available, configure voice search
*
@@ -384,346 +314,45 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen
* voice search.
*/
private void configureVoiceSearchButton() {
- boolean voiceSearchVisible = false;
- if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
- // Enable the voice search button if there is an activity that can handle it
- PackageManager pm = getContext().getPackageManager();
- ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
- PackageManager.MATCH_DEFAULT_ONLY);
- voiceSearchVisible = ri != null;
- }
-
+ // Enable the voice search button if there is an activity that can handle it
+ PackageManager pm = getContext().getPackageManager();
+ ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ boolean voiceSearchVisible = ri != null;
+
// finally, set visible state of voice search button, as appropriate
mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
}
-
- /** The rest of the class deals with providing search suggestions */
-
- /**
- * Set up the suggestions provider mechanism
- */
- private void configureSuggestions() {
- // get SearchableInfo
-
- mSearchText.setOnItemClickListener(this);
- mSearchText.setOnItemSelectedListener(this);
-
- // attach the suggestions adapter
- mSuggestionsAdapter = new SuggestionsAdapter(mContext,
- com.android.internal.R.layout.search_dropdown_item_2line, null,
- SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
- mSearchText.setAdapter(mSuggestionsAdapter);
- }
-
- /**
- * Remove internal cursor references when detaching from window which
- * prevents {@link Context} leaks.
- */
- @Override
- public void onDetachedFromWindow() {
- if (mSuggestionsAdapter != null) {
- mSuggestionsAdapter.changeCursor(null);
- mSuggestionsAdapter = null;
- }
- }
-
- /**
- * Implements OnItemClickListener
- */
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-// Log.d(TAG, "onItemClick() position " + position);
- launchSuggestion(mSuggestionsAdapter, position);
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-// Log.d(TAG, "onItemSelected() position " + position);
- mItemSelected = position;
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onNothingSelected(AdapterView<?> parent) {
-// Log.d(TAG, "onNothingSelected()");
- mItemSelected = -1;
- }
/**
- * Code to launch a suggestion query.
- *
- * This is copied from SearchDialog.
- *
- * @param ca The CursorAdapter containing the suggestions
- * @param position The suggestion we'll be launching from
- *
- * @return Returns true if a successful launch, false if could not (e.g. bad position)
+ * Sets the {@link Launcher} that this gadget will call on to display the search dialog.
*/
- private boolean launchSuggestion(CursorAdapter ca, int position) {
- if (ca != null) {
- Cursor c = ca.getCursor();
- if ((c != null) && c.moveToPosition(position)) {
- setupSuggestionIntent(c, mSearchable);
-
- SearchableInfo si = mSearchable;
- String suggestionAction = mSuggestionAction;
- Uri suggestionData = mSuggestionData;
- String suggestionQuery = mSuggestionQuery;
- sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
- KeyEvent.KEYCODE_UNKNOWN, null, si);
- clearQuery();
- return true;
- }
- }
- return false;
- }
-
- /**
- * When a particular suggestion has been selected, perform the various lookups required
- * to use the suggestion. This includes checking the cursor for suggestion-specific data,
- * and/or falling back to the XML for defaults; It also creates REST style Uri data when
- * the suggestion includes a data id.
- *
- * NOTE: Return values are in member variables mSuggestionAction, mSuggestionData and
- * mSuggestionQuery.
- *
- * This is copied from SearchDialog.
- *
- * @param c The suggestions cursor, moved to the row of the user's selection
- * @param si The searchable activity's info record
- */
- void setupSuggestionIntent(Cursor c, SearchableInfo si) {
- try {
- // use specific action if supplied, or default action if supplied, or fixed default
- mSuggestionAction = null;
- int column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
- if (column >= 0) {
- final String action = c.getString(column);
- if (action != null) {
- mSuggestionAction = action;
- }
- }
- if (mSuggestionAction == null) {
- mSuggestionAction = si.getSuggestIntentAction();
- }
- if (mSuggestionAction == null) {
- mSuggestionAction = Intent.ACTION_SEARCH;
- }
-
- // use specific data if supplied, or default data if supplied
- String data = null;
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (column >= 0) {
- final String rowData = c.getString(column);
- if (rowData != null) {
- data = rowData;
- }
- }
- if (data == null) {
- data = si.getSuggestIntentData();
- }
-
- // then, if an ID was provided, append it.
- if (data != null) {
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
- if (column >= 0) {
- final String id = c.getString(column);
- if (id != null) {
- data = data + "/" + Uri.encode(id);
- }
- }
- }
- mSuggestionData = (data == null) ? null : Uri.parse(data);
-
- mSuggestionQuery = null;
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
- if (column >= 0) {
- final String query = c.getString(column);
- if (query != null) {
- mSuggestionQuery = query;
- }
- }
- } catch (RuntimeException e ) {
- int rowNum;
- try { // be really paranoid now
- rowNum = c.getPosition();
- } catch (RuntimeException e2 ) {
- rowNum = -1;
- }
- Log.w(TAG, "Search Suggestions cursor at row " + rowNum +
- " returned exception" + e.toString());
- }
- }
-
- SearchAutoCompleteTextView getSearchInputField() {
- return (SearchAutoCompleteTextView) mSearchText;
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
}
- /**
- * This class provides the filtering-based interface to suggestions providers.
- * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
- * two-line suggestions, but it does not support icons.
+ /**
+ * Moves the view to the top left corner of its parent.
*/
- private static class SuggestionsAdapter extends SimpleCursorAdapter {
- public final static String[] TWO_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
- SearchManager.SUGGEST_COLUMN_TEXT_2 };
- public final static int[] TWO_LINE_TO = {com.android.internal.R.id.text1,
- com.android.internal.R.id.text2};
-
- private final String TAG = "SuggestionsAdapter";
-
- Filter mFilter;
- SearchableInfo mSearchable;
- private Resources mProviderResources;
- String[] mFromStrings;
-
- public SuggestionsAdapter(Context context, int layout, Cursor c,
- String[] from, int[] to, SearchableInfo searchable) {
- super(context, layout, c, from, to);
- mFromStrings = from;
- mSearchable = searchable;
-
- // set up provider resources (gives us icons, etc.)
- Context activityContext = mSearchable.getActivityContext(mContext);
- Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
- mProviderResources = providerContext.getResources();
- }
-
- /**
- * Use the search suggestions provider to obtain a live cursor. This will be called
- * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
- * The results will be processed in the UI thread and changeCursor() will be called.
- */
+ private class ToParentOriginAnimation extends Animation {
@Override
- public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
- String query = (constraint == null) ? "" : constraint.toString();
- return getSuggestions(mSearchable, query);
- }
-
- /**
- * Overriding this allows us to write the selected query back into the box.
- * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does
- * not universally support the search API. But it is sufficient for Google Search.
- */
- @Override
- public CharSequence convertToString(Cursor cursor) {
- CharSequence result = null;
- if (cursor != null) {
- int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
- if (column >= 0) {
- final String query = cursor.getString(column);
- if (query != null) {
- result = query;
- }
- }
- }
- return result;
- }
-
- /**
- * Get the query cursor for the search suggestions.
- *
- * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it
- * could be hoisted into SearchableInfo or some other shared spot.
- *
- * @param query The search text entered (so far)
- * @return Returns a cursor with suggestions, or null if no suggestions
- */
- private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
- Cursor cursor = null;
- if (searchable.getSuggestAuthority() != null) {
- try {
- StringBuilder uriStr = new StringBuilder("content://");
- uriStr.append(searchable.getSuggestAuthority());
-
- // if content path provided, insert it now
- final String contentPath = searchable.getSuggestPath();
- if (contentPath != null) {
- uriStr.append('/');
- uriStr.append(contentPath);
- }
-
- // append standard suggestion query path
- uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
-
- // inject query, either as selection args or inline
- String[] selArgs = null;
- if (searchable.getSuggestSelection() != null) { // use selection if provided
- selArgs = new String[] {query};
- } else {
- uriStr.append('/'); // no sel, use REST pattern
- uriStr.append(Uri.encode(query));
- }
-
- // finally, make the query
- cursor = mContext.getContentResolver().query(
- Uri.parse(uriStr.toString()), null,
- searchable.getSuggestSelection(), selArgs,
- null);
- } catch (RuntimeException e) {
- Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
- cursor = null;
- }
- }
-
- return cursor;
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float dx = -getLeft() * interpolatedTime;
+ float dy = -getTop() * interpolatedTime;
+ t.getMatrix().setTranslate(dx, dy);
}
+ }
- /**
- * Overriding this allows us to affect the way that an icon is loaded. Specifically,
- * we can be more controlling about the resource path (and allow icons to come from other
- * packages).
- *
- * TODO: This is 100% identical to the version in SearchDialog.java
- *
- * @param v ImageView to receive an image
- * @param value the value retrieved from the cursor
- */
+ /**
+ * Moves the view from the top left corner of its parent.
+ */
+ private class FromParentOriginAnimation extends Animation {
@Override
- public void setViewImage(ImageView v, String value) {
- int resID;
- Drawable img = null;
-
- try {
- resID = Integer.parseInt(value);
- if (resID != 0) {
- img = mProviderResources.getDrawable(resID);
- }
- } catch (NumberFormatException nfe) {
- // img = null;
- } catch (NotFoundException e2) {
- // img = null;
- }
-
- // finally, set the image to whatever we've gotten
- v.setImageDrawable(img);
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float dx = -getLeft() * (1.0f - interpolatedTime);
+ float dy = -getTop() * (1.0f - interpolatedTime);
+ t.getMatrix().setTranslate(dx, dy);
}
-
- /**
- * This method is overridden purely to provide a bit of protection against
- * flaky content providers.
- *
- * TODO: This is 100% identical to the version in SearchDialog.java
- *
- * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- try {
- return super.getView(position, convertView, parent);
- } catch (RuntimeException e) {
- Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
- // what can I return here?
- View v = newView(mContext, mCursor, parent);
- if (v != null) {
- TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
- tv.setText(e.toString());
- }
- return v;
- }
- }
-
}
+
}
diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java
index 12bdf7d0a..d474efa60 100644
--- a/src/com/android/launcher/Workspace.java
+++ b/src/com/android/launcher/Workspace.java
@@ -1100,7 +1100,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
return result;
}
-
+
/**
* Find a search widget on the given screen
*/
@@ -1114,102 +1114,14 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag
}
return null;
}
-
- /**
- * Focuses on the search widget on the specified screen,
- * if there is one. Also clears the current search selection so we don't
- */
- private boolean focusOnSearch(int screen) {
- CellLayout currentScreen = (CellLayout) getChildAt(screen);
- final Search searchWidget = findSearchWidget(currentScreen);
- if (searchWidget != null) {
- // This is necessary when focus on search is requested from the menu
- // If the workspace was not in touch mode before the menu is invoked
- // and the user clicks "Search" by touching the menu item, the following
- // happens:
- //
- // - We request focus from touch on the search widget
- // - The search widget gains focus
- // - The window focus comes back to Home's window
- // - The touch mode change is propagated to Home's window
- // - The search widget is not focusable in touch mode and ViewRoot
- // clears its focus
- //
- // Forcing focusable in touch mode ensures the search widget will
- // keep the focus no matter what happens.
- //
- // Note: the search input field disables focusable in touch mode
- // after the window gets the focus back, see SearchAutoCompleteTextView
- final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
- input.setFocusableInTouchMode(true);
- input.showKeyboardOnNextFocus();
-
- if (isInTouchMode()) {
- searchWidget.requestFocusFromTouch();
- } else {
- searchWidget.requestFocus();
- }
- searchWidget.clearQuery();
- return true;
- }
- return false;
- }
-
+
/**
- * Snap to the nearest screen with a search widget and give it focus
- *
- * @return True if a search widget was found
+ * Gets the first search widget on the current screen, if there is one.
+ * Returns <code>null</code> otherwise.
*/
- public boolean snapToSearch() {
- // The screen we are searching
- int current = mCurrentScreen;
-
- // first position scanned so far
- int first = current;
-
- // last position scanned so far
- int last = current;
-
- // True if we should move down on the next iteration
- boolean next = false;
-
- // True when we have looked at the first item in the data
- boolean hitFirst;
-
- // True when we have looked at the last item in the data
- boolean hitLast;
-
- final int count = getChildCount();
-
- while (true) {
- if (focusOnSearch(current)) {
- return true;
- }
-
- hitLast = last == count - 1;
- hitFirst = first == 0;
-
- if (hitLast && hitFirst) {
- // Looked at everything
- break;
- }
-
- if (hitFirst || (next && !hitLast)) {
- // Either we hit the top, or we are trying to move down
- last++;
- current = last;
- // Try going up next time
- next = false;
- } else {
- // Either we hit the bottom, or we are trying to move up
- first--;
- current = first;
- // Try going down next time
- next = true;
- }
-
- }
- return false;
+ public Search findSearchWidgetOnCurrentScreen() {
+ CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
+ return findSearchWidget(currentScreen);
}
public Folder getFolderForTag(Object tag) {