Copy SearchSourceSelector from QuickSearchBox

The widget shows the icon for a search source, and when clicked
fires an intent that shows a search source selection activity.
That intent is handled by the QuickSearchBox app.

This change also adds the source selector to the in-app
search dialog.

An upcoming change to QuickSearchBox will add the search source
selector to the home screen search widget and to the Quick Search Box
activity.

TODO: Add assets for selected and pressed states to the search selector.
TODO: The SearchDialog hides when the soruce selection activity
appears. This will be fixed when SearchDialog is changed from a
system window to a normal app window.

Change-Id: I91eadd60682577614e274ecf5b995b927c70a48a
This commit is contained in:
Bjorn Bringert
2010-01-06 09:29:11 +00:00
parent 71de7851a2
commit 32d580c360
9 changed files with 290 additions and 14 deletions

View File

@@ -23917,6 +23917,17 @@
visibility="public"
>
</field>
<field name="INTENT_ACTION_SELECT_SEARCH_SOURCE"
type="java.lang.String"
transient="false"
volatile="false"
value="&quot;android.intent.action.SELECT_SEARCH_SOURCE&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="INTENT_ACTION_WEB_SEARCH_SETTINGS"
type="java.lang.String"
transient="false"

View File

@@ -60,7 +60,6 @@ import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
@@ -106,7 +105,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// views & widgets
private TextView mBadgeLabel;
private ImageView mAppIcon;
private SearchSourceSelector mSourceSelector;
private SearchAutoComplete mSearchAutoComplete;
private Button mGoButton;
private ImageButton mVoiceButton;
@@ -209,7 +208,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
mSearchAutoComplete = (SearchAutoComplete)
findViewById(com.android.internal.R.id.search_src_text);
mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
mSourceSelector = new SearchSourceSelector(
findViewById(com.android.internal.R.id.search_source_selector));
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
@@ -606,13 +606,16 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
private void updateSearchAppIcon() {
mSourceSelector.setSource(mSearchable.getSearchActivity());
mSourceSelector.setAppSearchData(mAppSearchData);
// In Donut, we special-case the case of the browser to hide the app icon as if it were
// global search, for extra space for url entry.
//
// TODO: Remove this special case once the issue has been reconciled in Eclair.
if (mGlobalSearchMode || isBrowserSearch()) {
mAppIcon.setImageResource(0);
mAppIcon.setVisibility(View.GONE);
mSourceSelector.setSourceIcon(null);
mSourceSelector.setVisibility(View.GONE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -628,8 +631,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
icon = pm.getDefaultActivityIcon();
Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
}
mAppIcon.setImageDrawable(icon);
mAppIcon.setVisibility(View.VISIBLE);
mSourceSelector.setSourceIcon(icon);
mSourceSelector.setVisibility(View.VISIBLE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -812,6 +815,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (!mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, remember it.
mUserQuery = s == null ? "" : s.toString();
mSourceSelector.setQuery(mUserQuery);
}
}
@@ -1927,6 +1931,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
query = "";
}
mUserQuery = query;
mSourceSelector.setQuery(query);
mSearchAutoComplete.setText(query);
mSearchAutoComplete.setSelection(query.length());
}

View File

@@ -1346,6 +1346,7 @@ public class SearchManager
* @hide Pending API council approval
*/
public final static String SELECT_INITIAL_QUERY = "select_initial_query";
/**
* Defines the constants used in the communication between {@link android.app.SearchDialog} and
* the global search provider via {@link Cursor#respond(android.os.Bundle)}.
@@ -1611,6 +1612,15 @@ public class SearchManager
*/
public final static String SUGGEST_PARAMETER_LIMIT = "limit";
/**
* Intent action for opening the search source selection activity.
* The intent may include these extra values:
* {@link #QUERY},
* {@link #APP_DATA}.
*/
public static final String INTENT_ACTION_SELECT_SEARCH_SOURCE
= "android.intent.action.SELECT_SEARCH_SOURCE";
/**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
* the search dialog will switch to a different suggestion source when the

View File

@@ -0,0 +1,197 @@
/*
* 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.
*/
package android.app;
import com.android.internal.R;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import java.util.List;
/**
* Utilities for setting up the search source selector.
*
* This class has two copies:
* android.app.SearchSourceSelector
* com.android.quicksearchbox.ui.SearchSourceSelector
*
* They should keep the same look and feel as much as possible,
* but only the intent details must absolutely stay in sync.
*
* @hide
*/
public class SearchSourceSelector implements View.OnClickListener {
private static final String TAG = "SearchSourceSelector";
// TODO: This should be defined in android.provider.Applications,
// and have a less made-up value.
private static final String APPLICATION_TYPE = "application/vnd.android.application";
public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
private final View mView;
private final ImageButton mIconView;
private ComponentName mSource;
private Bundle mAppSearchData;
private String mQuery;
public SearchSourceSelector(View view) {
mView = view;
mIconView = (ImageButton) view.findViewById(ICON_VIEW_ID);
mIconView.setOnClickListener(this);
}
/**
* Sets the icon displayed in the search source selector.
*/
public void setSourceIcon(Drawable icon) {
mIconView.setImageDrawable(icon);
}
/**
* Sets the current search source.
*/
public void setSource(ComponentName source) {
mSource = source;
}
/**
* Sets the app-specific data that will be passed to the search activity if
* the user opens the source selector and chooses a source.
*/
public void setAppSearchData(Bundle appSearchData) {
mAppSearchData = appSearchData;
}
/**
* Sets the initial query that will be passed to the search activity if
* the user opens the source selector and chooses a source.
*/
public void setQuery(String query) {
mQuery = query;
}
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
}
/**
* Creates an intent for opening the search source selector activity.
*
* @param source The current search source.
* @param query The initial query that will be passed to the search activity if
* the user opens the source selector and chooses a source.
* @param appSearchData The app-specific data that will be passed to the search
* activity if the user opens the source selector and chooses a source.
*/
public static Intent createIntent(ComponentName source, String query, Bundle appSearchData) {
Intent intent = new Intent(SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Uri sourceUri = componentNameToUri(source);
if (sourceUri != null) {
intent.setDataAndType(sourceUri, APPLICATION_TYPE);
}
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
if (query != null) {
intent.putExtra(SearchManager.APP_DATA, appSearchData);
}
return intent;
}
/**
* Gets the search source from which the given
* {@link SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE} intent was sent.
*/
public static ComponentName getSource(Intent intent) {
return uriToComponentName(intent.getData());
}
private static Uri componentNameToUri(ComponentName name) {
if (name == null) return null;
// TODO: This URI format is specificed in android.provider.Applications which is @hidden
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority("applications")
.appendEncodedPath("applications")
.appendPath(name.getPackageName())
.appendPath(name.getClassName())
.build();
}
private static ComponentName uriToComponentName(Uri uri) {
if (uri == null) return null;
List<String> path = uri.getPathSegments();
if (path == null || path.size() != 3) return null;
String pkg = path.get(1);
String cls = path.get(2);
if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(cls)) return null;
return new ComponentName(pkg, cls);
}
public void onClick(View v) {
trigger();
}
private void trigger() {
try {
Intent intent = createIntent(mSource, mQuery, mAppSearchData);
intent.setSourceBounds(getOnScreenRect(mIconView));
mIconView.getContext().startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "No source selector activity found", ex);
}
}
// TODO: This code is replicated in lots of places:
// - android.provider.ContactsContract.QuickContact.showQuickContact()
// - android.widget.RemoteViews.setOnClickPendingIntent()
// - com.android.launcher2.Launcher.onClick()
// - com.android.launcher.Launcher.onClick()
// - com.android.server.status.StatusBarService.Launcher.onClick()
private static Rect getOnScreenRect(View v) {
final float appScale = v.getResources().getCompatibilityInfo().applicationScale;
final int[] pos = new int[2];
v.getLocationOnScreen(pos);
final Rect rect = new Rect();
rect.left = (int) (pos[0] * appScale + 0.5f);
rect.top = (int) (pos[1] * appScale + 0.5f);
rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
return rect;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

View File

@@ -0,0 +1,21 @@
<?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">
<!-- TODO: Need focused and pressed backgrounds -->
</selector>

View File

@@ -55,14 +55,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/search_app_icon"
android:layout_height="36dip"
android:layout_width="36dip"
<include android:id="@+id/search_source_selector"
layout="@layout/search_source_selector"
android:layout_marginRight="7dip"
android:layout_gravity="center_vertical"
/>
android:layout_gravity="center_vertical" />
<view class="android.app.SearchDialog$SearchAutoComplete"
android:id="@+id/search_src_text"

View File

@@ -0,0 +1,35 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dip"
android:layout_height="match_parent"
android:foreground="@drawable/search_source_selector_indicator"
android:foregroundGravity="bottom|right"
>
<ImageButton
android:id="@+id/search_source_selector_icon"
android:background="@drawable/search_source_selector_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:focusable="true"
android:clickable="true"
/>
</FrameLayout>