Merge "Add voice interaction support to ResolverActivity/ChooserActivity" into mnc-dev
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 678e92b..1b55557 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -100,6 +100,10 @@
                     mChooserListAdapter.addServiceResults(sri.originalTarget, sri.resultTargets);
                     unbindService(sri.connection);
                     mServiceConnections.remove(sri.connection);
+                    if (mServiceConnections.isEmpty()) {
+                        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+                        sendVoiceChoicesIfNeeded();
+                    }
                     break;
 
                 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
@@ -107,6 +111,7 @@
                         Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
                     }
                     unbindRemainingServices();
+                    sendVoiceChoicesIfNeeded();
                     break;
 
                 default:
@@ -384,6 +389,8 @@
                     + WATCHDOG_TIMEOUT_MILLIS + "ms");
             mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
                     WATCHDOG_TIMEOUT_MILLIS);
+        } else {
+            sendVoiceChoicesIfNeeded();
         }
     }
 
@@ -418,6 +425,10 @@
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
     }
 
+    void onSetupVoiceInteraction() {
+        // Do nothing. We'll send the voice stuff ourselves.
+    }
+
     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
         if (mRefinementResultReceiver != null) {
             mRefinementResultReceiver.destroy();
@@ -956,6 +967,10 @@
             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
             unbindService(this);
             mServiceConnections.remove(this);
+            if (mServiceConnections.isEmpty()) {
+                mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+                sendVoiceChoicesIfNeeded();
+            }
         }
 
         @Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index e14f058..fe3ab9e 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -16,10 +16,17 @@
 
 package com.android.internal.app;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
+import android.app.VoiceInteractor;
+import android.app.VoiceInteractor.PickOptionRequest;
+import android.app.VoiceInteractor.PickOptionRequest.Option;
+import android.app.VoiceInteractor.Prompt;
+import android.app.VoiceInteractor.Request;
 import android.os.AsyncTask;
 import android.provider.Settings;
+import android.service.chooser.ChooserTarget;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.widget.AbsListView;
@@ -96,6 +103,7 @@
     private int mProfileSwitchMessageId = -1;
     private final ArrayList<Intent> mIntents = new ArrayList<>();
     private ResolverComparator mResolverComparator;
+    private PickTargetOptionRequest mPickOptionRequest;
 
     private boolean mRegistered;
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -242,6 +250,9 @@
                     finish();
                 }
             });
+            if (isVoiceInteraction()) {
+                rdl.setCollapsed(false);
+            }
         }
 
         if (title == null) {
@@ -313,6 +324,39 @@
             });
             bindProfileView();
         }
+
+        if (isVoiceInteraction()) {
+            onSetupVoiceInteraction();
+        }
+    }
+
+    /**
+     * Perform any initialization needed for voice interaction.
+     */
+    void onSetupVoiceInteraction() {
+        // Do it right now. Subclasses may delay this and send it later.
+        sendVoiceChoicesIfNeeded();
+    }
+
+    void sendVoiceChoicesIfNeeded() {
+        if (!isVoiceInteraction()) {
+            // Clearly not needed.
+            return;
+        }
+
+
+        final Option[] options = new Option[mAdapter.getCount()];
+        for (int i = 0, N = options.length; i < N; i++) {
+            options[i] = optionForChooserTarget(mAdapter.getItem(i), i);
+        }
+
+        mPickOptionRequest = new PickTargetOptionRequest(
+                new Prompt(getTitle()), options, null);
+        getVoiceInteractor().submitRequest(mPickOptionRequest);
+    }
+
+    Option optionForChooserTarget(TargetInfo target, int index) {
+        return new Option(target.getDisplayLabel(), index);
     }
 
     protected final void setAdditionalTargets(Intent[] intents) {
@@ -473,6 +517,14 @@
     }
 
     @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (!isChangingConfigurations() && mPickOptionRequest != null) {
+            mPickOptionRequest.cancel();
+        }
+    }
+
+    @Override
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         super.onRestoreInstanceState(savedInstanceState);
         if (mAlwaysUseOption) {
@@ -510,16 +562,12 @@
         try {
             ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
                     resolveInfo.activityInfo.packageName, 0 /* default flags */);
-            return versionNumberAtLeastL(appInfo.targetSdkVersion);
+            return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
         } catch (NameNotFoundException e) {
             return false;
         }
     }
 
-    private boolean versionNumberAtLeastL(int versionNumber) {
-        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
-    }
-
     private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
             boolean filtered) {
         boolean enabled = false;
@@ -1644,4 +1692,39 @@
                 && match <= IntentFilter.MATCH_CATEGORY_PATH;
     }
 
+    static class PickTargetOptionRequest extends PickOptionRequest {
+        public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
+                @Nullable Bundle extras) {
+            super(prompt, options, extras);
+        }
+
+        @Override
+        public void onCancel() {
+            super.onCancel();
+            final ResolverActivity ra = (ResolverActivity) getActivity();
+            if (ra != null) {
+                ra.mPickOptionRequest = null;
+                ra.finish();
+            }
+        }
+
+        @Override
+        public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
+            super.onPickOptionResult(finished, selections, result);
+            if (selections.length != 1) {
+                // TODO In a better world we would filter the UI presented here and let the
+                // user refine. Maybe later.
+                return;
+            }
+
+            final ResolverActivity ra = (ResolverActivity) getActivity();
+            if (ra != null) {
+                final TargetInfo ti = ra.mAdapter.getItem(selections[0].getIndex());
+                if (ra.onTargetSelected(ti, false)) {
+                    ra.mPickOptionRequest = null;
+                    ra.finish();
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 585cbc9..1071e12 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -144,6 +144,14 @@
         return mCollapseOffset > 0;
     }
 
+    public void setCollapsed(boolean collapsed) {
+        if (!isLaidOut()) {
+            mOpenOnLayout = collapsed;
+        } else {
+            smoothScrollTo(collapsed ? mCollapsibleHeight : 0, 0);
+        }
+    }
+
     private boolean isMoving() {
         return mIsDragging || !mScroller.isFinished();
     }
@@ -575,7 +583,13 @@
     @Override
     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
         if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
-            smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
+            if (mOnDismissedListener != null
+                    && velocityY < 0 && mCollapseOffset > mCollapsibleHeight) {
+                smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, velocityY);
+                mDismissOnScrollerFinished = true;
+            } else {
+                smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
+            }
             return true;
         }
         return false;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b1b772a..f9b41a93 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2457,6 +2457,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"