From 4c470d641c6906a742b63273b305695ba575b3e1 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Fri, 19 Jun 2015 17:46:17 -0700 Subject: [PATCH] Add voice interaction support to ResolverActivity/ChooserActivity All options are sent to the VoiceInteractor once ChooserTargetServices have reported in. We don't perform explicit progressive refinement or filtering, but an explicit option picked will be invoked. Also fix a lingering bug around being able to nested-fling the resolver drawer closed. Bug 21516866 Change-Id: I6b141f5fa87d74dccec9dcb88110630696e9c38e --- .../android/internal/app/ChooserActivity.java | 15 +++ .../internal/app/ResolverActivity.java | 93 ++++++++++++++++++- .../internal/widget/ResolverDrawerLayout.java | 16 +++- core/res/AndroidManifest.xml | 1 + 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 678e92b35db88..1b55557e8dd7a 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -100,6 +100,10 @@ public class ChooserActivity extends ResolverActivity { 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 @@ public class ChooserActivity extends ResolverActivity { Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); } unbindRemainingServices(); + sendVoiceChoicesIfNeeded(); break; default: @@ -384,6 +389,8 @@ public class ChooserActivity extends ResolverActivity { + WATCHDOG_TIMEOUT_MILLIS + "ms"); mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, WATCHDOG_TIMEOUT_MILLIS); + } else { + sendVoiceChoicesIfNeeded(); } } @@ -418,6 +425,10 @@ public class ChooserActivity extends ResolverActivity { 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 @@ public class ChooserActivity extends ResolverActivity { 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 e14f0584a0b80..fe3ab9e497437 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 @@ public class ResolverActivity extends Activity { private int mProfileSwitchMessageId = -1; private final ArrayList mIntents = new ArrayList<>(); private ResolverComparator mResolverComparator; + private PickTargetOptionRequest mPickOptionRequest; private boolean mRegistered; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @@ -242,6 +250,9 @@ public class ResolverActivity extends Activity { finish(); } }); + if (isVoiceInteraction()) { + rdl.setCollapsed(false); + } } if (title == null) { @@ -313,6 +324,39 @@ public class ResolverActivity extends Activity { }); 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) { @@ -472,6 +516,14 @@ public class ResolverActivity extends Activity { } } + @Override + protected void onDestroy() { + super.onDestroy(); + if (!isChangingConfigurations() && mPickOptionRequest != null) { + mPickOptionRequest.cancel(); + } + } + @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); @@ -510,16 +562,12 @@ public class ResolverActivity extends Activity { 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 @@ public class ResolverActivity extends Activity { && 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 585cbc9437883..1071e12cb6e33 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -144,6 +144,14 @@ public class ResolverDrawerLayout extends ViewGroup { 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 @@ public class ResolverDrawerLayout extends ViewGroup { @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 b1b772ae7e7e2..f9b41a9324d47 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2457,6 +2457,7 @@ +