Allow external apps to add shortcuts
This is needed to get Voice Search shortcuts into QSB
Bug http://b/issue?id=2365954
Change-Id: I59bdb2b2b885417ef9d1871af5c9f838063d622d
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 061b800..61756aa 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -62,8 +62,13 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="qsb.corpus" />
</intent-filter>
+ <meta-data android:name="android.app.search.shortcut.provider" android:value="content://com.android.quicksearchbox.shortcuts/shortcuts" />
</activity>
+ <provider android:name=".ShortcutsProvider"
+ android:authorities="com.android.quicksearchbox.shortcuts">
+ </provider>
+
<activity android:name=".SearchSettings"
android:label="@string/search_settings"
android:excludeFromRecents="true">
diff --git a/src/com/android/quicksearchbox/ShortcutsProvider.java b/src/com/android/quicksearchbox/ShortcutsProvider.java
new file mode 100644
index 0000000..80fcfa7
--- /dev/null
+++ b/src/com/android/quicksearchbox/ShortcutsProvider.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010 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 com.android.quicksearchbox;
+
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Handles broadcast intents for adding shortcuts to QSB.
+ */
+public class ShortcutsProvider extends ContentProvider {
+
+ private static final boolean DBG = true;
+ private static final String TAG = "QSB.ExternalShortcutReceiver";
+
+ public static final String EXTRA_SHORTCUT_SOURCE = "shortcut_source";
+
+ private static final int SHORTCUTS = 0;
+
+ private UriMatcher mUriMatcher;
+
+ @Override
+ public boolean onCreate() {
+ mUriMatcher = buildUriMatcher();
+ return true;
+ }
+
+ private UriMatcher buildUriMatcher() {
+ String authority = getAuthority();
+ UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ matcher.addURI(authority, "shortcuts", SHORTCUTS);
+ return matcher;
+ }
+
+ private String getAuthority() {
+ return getContext().getPackageName() + ".shortcuts";
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (mUriMatcher.match(uri)) {
+ case SHORTCUTS:
+ return SearchManager.SUGGEST_MIME_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ switch (mUriMatcher.match(uri)) {
+ case SHORTCUTS:
+ addShortcut(values);
+ return null;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ private void addShortcut(final ContentValues shortcut) {
+ String sourceName = shortcut.getAsString(EXTRA_SHORTCUT_SOURCE);
+ if (TextUtils.isEmpty(sourceName)) {
+ Log.e(TAG, "Missing " + EXTRA_SHORTCUT_SOURCE);
+ return;
+ }
+ final ComponentName sourceComponent = ComponentName.unflattenFromString(sourceName);
+ if (!checkCallingPackage(sourceComponent.getPackageName())) {
+ Log.w(TAG, "Got shortcut for " + sourceComponent + " from a different process");
+ return;
+ }
+
+ getQsbApplication().runOnUiThread(new Runnable() {
+ public void run() {
+ storeShortcut(sourceComponent, shortcut);
+ }
+ });
+ }
+
+ // Called on the main thread
+ private void storeShortcut(ComponentName sourceComponent, ContentValues shortcut) {
+ if (DBG) Log.d(TAG, "Adding (PID: " + Binder.getCallingPid() + "): " + shortcut);
+
+ Source source = getCorpora().getSource(sourceComponent.flattenToShortString());
+ if (source == null) {
+ Log.w(TAG, "Unknown shortcut source " + sourceComponent);
+ return;
+ }
+
+ String userQuery = shortcut.getAsString(SearchManager.USER_QUERY);
+ if (userQuery == null) userQuery = "";
+
+ DataSuggestionCursor cursor = new DataSuggestionCursor(userQuery);
+ cursor.add(makeSuggestion(source, shortcut));
+ getShortcutRepository().reportClick(cursor, 0);
+ }
+
+ private boolean checkCallingPackage(String packageName) {
+ int callingUid = Binder.getCallingUid();
+ PackageManager pm = getContext().getPackageManager();
+ String[] uidPkgs = pm.getPackagesForUid(callingUid);
+ if (uidPkgs == null) return false;
+ for (String uidPkg : uidPkgs) {
+ if (packageName.equals(uidPkg)) return true;
+ }
+ return false;
+ }
+
+ private SuggestionData makeSuggestion(Source source, ContentValues shortcut) {
+ String format = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_FORMAT);
+ String text1 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ String text2 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_2);
+ String text2Url = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+ String icon1 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_ICON_1);
+ String icon2 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_ICON_2);
+ String shortcutId = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
+ boolean spinnerWhileRefreshing = unboxBoolean(
+ shortcut.getAsBoolean(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING),
+ false);
+ String intentAction = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+ String intentData = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+ String intentExtraData =
+ shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
+ String query = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_QUERY);
+
+ SuggestionData suggestion = new SuggestionData(source);
+ suggestion.setFormat(format);
+ suggestion.setText1(text1);
+ suggestion.setText2(text2);
+ suggestion.setText2Url(text2Url);
+ suggestion.setIcon1(icon1);
+ suggestion.setIcon2(icon2);
+ suggestion.setShortcutId(shortcutId);
+ suggestion.setSpinnerWhileRefreshing(spinnerWhileRefreshing);
+ suggestion.setIntentAction(intentAction);
+ suggestion.setIntentData(intentData);
+ suggestion.setIntentExtraData(intentExtraData);
+ suggestion.setSuggestionQuery(query);
+ return suggestion;
+ }
+
+ private static boolean unboxBoolean(Boolean value, boolean defValue) {
+ return value == null ? defValue : value;
+ }
+
+ private QsbApplication getQsbApplication() {
+ return (QsbApplication) getContext().getApplicationContext();
+ }
+
+ private ShortcutRepository getShortcutRepository() {
+ return getQsbApplication().getShortcutRepository();
+ }
+
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+}