Merge branch 'readonly-p4-donut' into donut
This commit is contained in:
commit
71b0594a6f
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 |
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<clock
|
||||
launcher:screen="2"
|
||||
launcher:x="1"
|
||||
launcher:y="0" />
|
||||
launcher:y="1" />
|
||||
|
||||
<search
|
||||
launcher:screen="1"
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue