Merge branch 'readonly-p4-donut' into donut

This commit is contained in:
Karl Rosaen 2009-04-23 19:01:37 -07:00 committed by The Android Open Source Project
commit 71b0594a6f
12 changed files with 459 additions and 852 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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>

View File

@ -19,7 +19,7 @@
<clock
launcher:screen="2"
launcher:x="1"
launcher:y="0" />
launcher:y="1" />
<search
launcher:screen="1"

View File

@ -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.GridView;
import android.widget.ListView;
import android.widget.SlidingDrawer;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.GridView;
import android.widget.SlidingDrawer;
import android.app.IWallpaperService;
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);
@ -1890,21 +1932,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.
*/
@ -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
}
}
}

View File

@ -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 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");
}
}
/**
* 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.
*/
public void setQuery(String query) {
mSearchText.setText(query, TextView.BufferType.NORMAL);
}
/**
* 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);
}
if (isAtTop()) {
showSearchDialog();
} else {
// Start the animation, unless it has already started.
if (getAnimation() != mMorphAnimation) {
mMorphAnimation.setDuration(getAnimationDuration());
startAnimation(mMorphAnimation);
}
}
}
private void query() {
String query = mSearchText.getText().toString();
if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
return;
/**
* Shows the system search dialog immediately, without any animation.
*/
private void showSearchDialog() {
mLauncher.showSearchDialog(
mInitialQuery, mSelectInitialQuery, mAppSearchData, mGlobalSearch);
}
/**
* Restore the search gadget to its normal position.
*
* @param animate Whether to animate the movement of the gadget.
*/
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();
}
}
}
private boolean isAtTop() {
return getTop() == 0;
}
private int getAnimationDuration() {
return (int) (getTop() / ANIMATION_VELOCITY);
}
/**
* 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.
*/
@Override
public void clearAnimation() {
Animation animation = getAnimation();
if (animation != null) {
super.clearAnimation();
if (animation.hasEnded()
&& animation.getFillAfter()
&& animation.willChangeBounds()) {
((View) getParent()).invalidate();
} else {
invalidate();
}
}
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.
*/
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);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus && hasFocus()) {
mRestoreFocus = true;
}
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus && mRestoreFocus) {
if (isInTouchMode()) {
final AutoCompleteTextView searchText = mSearchText;
searchText.setSelectAllOnFocus(false);
searchText.requestFocusFromTouch();
searchText.setSelectAllOnFocus(true);
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);
}
mRestoreFocus = false;
}
}
/**
* Implements TextWatcher (for EditText)
*/
public void beforeTextChanged(CharSequence s, int start, int before, int after) {
}
/**
* Implements TextWatcher (for EditText)
*/
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);
}
/**
* Implements TextWatcher (for EditText)
*/
public void afterTextChanged(Editable s) {
}
/**
* 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.
*/
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;
}
}
} else {
// Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
// " mItemSelected="+ mItemSelected);
if (searchTrigger && mItemSelected < 0) {
query();
return true;
}
}
} 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)
* 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 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);
}
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.
*/
public void clearQuery() {
mSearchText.setText(null);
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.setOnKeyListener(this);
mSearchText.addTextChangedListener(this);
mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
mSearchText = (TextView) findViewById(R.id.search_src_text);
mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
mGoButton.setOnClickListener(this);
mSearchText.setOnKeyListener(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
* Sets the {@link Launcher} that this gadget will call on to display the search dialog.
*/
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);
public void setLauncher(Launcher launcher) {
mLauncher = launcher;
}
/**
* 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
* Moves the view to the top left corner of its parent.
*/
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)
*/
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;
}
/**
* 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.
*/
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);
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 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;
}
/**
* 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
*/
@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);
}
/**
* 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;
}
}
}
/**
* Moves the view from the top left corner of its parent.
*/
private class FromParentOriginAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = -getLeft() * (1.0f - interpolatedTime);
float dy = -getTop() * (1.0f - interpolatedTime);
t.getMatrix().setTranslate(dx, dy);
}
}
}

View File

@ -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
* Gets the first search widget on the current screen, if there is one.
* Returns <code>null</code> otherwise.
*/
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
*/
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) {