diff options
author | lpeter <lpeter@google.com> | 2021-09-13 14:33:40 +0800 |
---|---|---|
committer | lpeter <lpeter@google.com> | 2021-09-28 17:06:49 +0800 |
commit | 918ea9940f27815a3dba83e45de137e04889bc2d (patch) | |
tree | 96a87436e3c2c462f510400018bee84a7bfa80f4 | |
parent | 94ec7e0d2a78c91a443fd79bcae60feb5d4d93f8 (diff) |
[NGA v2] Reliable Visible Activity Lookup
To enable more voice-oriented in-app user journeys, Google Assistant
needs to access more information regarding the visible activities
via the Voice Interaction Session. Thus, we provide the APIs to receive
the changed visible activities in Voice Interaction Session.
Bug: 178244510
Test: atest CtsVoiceInteractionTestCases
Test: atest CtsVoiceInteractionTestCases --instant
Change-Id: I019d09fee8105ae1eadbc76803f46fd8b1948f6b
13 files changed, 717 insertions, 1 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 4df648cb7009..56df8f3cde16 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39001,6 +39001,13 @@ package android.service.textservice { package android.service.voice { + public final class VisibleActivityInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.VisibleActivityInfo> CREATOR; + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public int getDisabledShowContext(); @@ -39062,6 +39069,7 @@ package android.service.voice { method public void onTaskStarted(android.content.Intent, int); method public void onTrimMemory(int); method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>); + method public final void registerVisibleActivityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback); method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>); method public void setContentView(android.view.View); method public void setDisabledShowContext(int); @@ -39071,6 +39079,7 @@ package android.service.voice { method public void show(android.os.Bundle, int); method public void startAssistantActivity(android.content.Intent); method public void startVoiceActivity(android.content.Intent); + method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback); field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10 field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8 field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4 @@ -39144,6 +39153,11 @@ package android.service.voice { method public boolean isActive(); } + public static interface VoiceInteractionSession.VisibleActivityCallback { + method public default void onInvisible(@NonNull android.service.voice.VoiceInteractionSession.ActivityId); + method public default void onVisible(@NonNull android.service.voice.VisibleActivityInfo); + } + public abstract class VoiceInteractionSessionService extends android.app.Service { ctor public VoiceInteractionSessionService(); method public android.os.IBinder onBind(android.content.Intent); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2ecf088fb5d0..d484100373af 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2391,6 +2391,10 @@ package android.service.voice { method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]); } + public final class VisibleActivityInfo implements android.os.Parcelable { + ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder); + } + } package android.service.watchdog { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 70bb132c7668..f8c8aa32a26e 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -652,4 +652,23 @@ public abstract class ActivityManagerInternal { public abstract int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options); + + /** + * Sets the provider to communicate between voice interaction manager service and + * ActivityManagerService. + */ + public abstract void setVoiceInteractionManagerProvider( + @Nullable VoiceInteractionManagerProvider provider); + + /** + * Provides the interface to communicate between voice interaction manager service and + * ActivityManagerService. + */ + public interface VoiceInteractionManagerProvider { + /** + * Notifies the service when a high-level activity event has been changed, for example, + * an activity was resumed or stopped. + */ + void notifyActivityEventChanged(); + } } diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index c142a53e047e..59f1e8eed89c 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -22,6 +22,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.IBinder; +import android.service.voice.VisibleActivityInfo; import com.android.internal.app.IVoiceInteractionSessionShowCallback; @@ -39,4 +40,5 @@ oneway interface IVoiceInteractionSession { void closeSystemDialogs(); void onLockscreenShown(); void destroy(); + void updateVisibleActivityInfo(in VisibleActivityInfo visibleActivityInfo, int type); } diff --git a/core/java/android/service/voice/VisibleActivityInfo.aidl b/core/java/android/service/voice/VisibleActivityInfo.aidl new file mode 100644 index 000000000000..34bd57c15456 --- /dev/null +++ b/core/java/android/service/voice/VisibleActivityInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.service.voice; + +parcelable VisibleActivityInfo; diff --git a/core/java/android/service/voice/VisibleActivityInfo.java b/core/java/android/service/voice/VisibleActivityInfo.java new file mode 100644 index 000000000000..139544c76a50 --- /dev/null +++ b/core/java/android/service/voice/VisibleActivityInfo.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2021 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.service.voice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * The class is used to represent a visible activity information. The system provides this to + * services that need to know {@link android.service.voice.VoiceInteractionSession.ActivityId}. + */ +@DataClass( + genConstructor = false, + genEqualsHashCode = true, + genHiddenConstDefs = false, + genGetters = false, + genToString = true +) +public final class VisibleActivityInfo implements Parcelable { + + /** + * Indicates that it is a new visible activity. + * + * @hide + */ + public static final int TYPE_ACTIVITY_ADDED = 1; + + /** + * Indicates that it has become a invisible activity. + * + * @hide + */ + public static final int TYPE_ACTIVITY_REMOVED = 2; + + /** + * The identifier of the task this activity is in. + */ + private final int mTaskId; + + /** + * Token for targeting this activity for assist purposes. + */ + @NonNull + private final IBinder mAssistToken; + + /** @hide */ + @TestApi + public VisibleActivityInfo( + int taskId, + @NonNull IBinder assistToken) { + Objects.requireNonNull(assistToken); + mTaskId = taskId; + mAssistToken = assistToken; + } + + /** + * Returns the {@link android.service.voice.VoiceInteractionSession.ActivityId} of this + * visible activity which can be used to interact with an activity, for example through + * {@link VoiceInteractionSession#requestDirectActions(VoiceInteractionSession.ActivityId, + * CancellationSignal, Executor, Consumer)}. + */ + public @NonNull VoiceInteractionSession.ActivityId getActivityId() { + return new VoiceInteractionSession.ActivityId(mTaskId, mAssistToken); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "VisibleActivityInfo { " + + "taskId = " + mTaskId + ", " + + "assistToken = " + mAssistToken + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(VisibleActivityInfo other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + VisibleActivityInfo that = (VisibleActivityInfo) o; + //noinspection PointlessBooleanExpression + return true + && mTaskId == that.mTaskId + && Objects.equals(mAssistToken, that.mAssistToken); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mTaskId; + _hash = 31 * _hash + Objects.hashCode(mAssistToken); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mTaskId); + dest.writeStrongBinder(mAssistToken); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ VisibleActivityInfo(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int taskId = in.readInt(); + IBinder assistToken = (IBinder) in.readStrongBinder(); + + this.mTaskId = taskId; + this.mAssistToken = assistToken; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAssistToken); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<VisibleActivityInfo> CREATOR + = new Parcelable.Creator<VisibleActivityInfo>() { + @Override + public VisibleActivityInfo[] newArray(int size) { + return new VisibleActivityInfo[size]; + } + + @Override + public VisibleActivityInfo createFromParcel(@NonNull Parcel in) { + return new VisibleActivityInfo(in); + } + }; + + @DataClass.Generated( + time = 1632383555284L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java", + inputSignatures = "public static final int TYPE_ACTIVITY_ADDED\npublic static final int TYPE_ACTIVITY_REMOVED\nprivate final int mTaskId\nprivate final @android.annotation.NonNull android.os.IBinder mAssistToken\npublic @android.annotation.NonNull android.service.voice.VoiceInteractionSession.ActivityId getActivityId()\nclass VisibleActivityInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true, genHiddenConstDefs=false, genGetters=false, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java index c048286545c3..c80640910bc2 100644 --- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -22,7 +22,6 @@ import android.os.IBinder; import com.android.internal.annotations.Immutable; - /** * @hide * Private interface to the VoiceInteractionManagerService for use by ActivityManagerService. diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 725e20f2a74d..9db856a8762a 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -74,9 +74,11 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -177,6 +179,10 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall ICancellationSignal mKillCallback; + private final Map<VisibleActivityCallback, Executor> mVisibleActivityCallbacks = + new ArrayMap<>(); + private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, @@ -352,6 +358,13 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall public void destroy() { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); } + + @Override + public void updateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) { + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageIO(MSG_UPDATE_VISIBLE_ACTIVITY_INFO, type, + visibleActivityInfo)); + } }; /** @@ -843,6 +856,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall static final int MSG_SHOW = 106; static final int MSG_HIDE = 107; static final int MSG_ON_LOCKSCREEN_SHOWN = 108; + static final int MSG_UPDATE_VISIBLE_ACTIVITY_INFO = 109; + static final int MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK = 110; + static final int MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK = 111; class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override @@ -928,6 +944,27 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall if (DEBUG) Log.d(TAG, "onLockscreenShown"); onLockscreenShown(); break; + case MSG_UPDATE_VISIBLE_ACTIVITY_INFO: + if (DEBUG) { + Log.d(TAG, "doUpdateVisibleActivityInfo: visibleActivityInfo=" + msg.obj + + " type=" + msg.arg1); + } + doUpdateVisibleActivityInfo((VisibleActivityInfo) msg.obj, msg.arg1); + break; + case MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK: + if (DEBUG) { + Log.d(TAG, "doRegisterVisibleActivityCallback"); + } + args = (SomeArgs) msg.obj; + doRegisterVisibleActivityCallback((Executor) args.arg1, + (VisibleActivityCallback) args.arg2); + break; + case MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK: + if (DEBUG) { + Log.d(TAG, "doUnregisterVisibleActivityCallback"); + } + doUnregisterVisibleActivityCallback((VisibleActivityCallback) msg.obj); + break; } if (args != null) { args.recycle(); @@ -1122,6 +1159,86 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } } + private void doUpdateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) { + + if (mVisibleActivityCallbacks.isEmpty()) { + return; + } + + switch (type) { + case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: + informVisibleActivityChanged(visibleActivityInfo, type); + mVisibleActivityInfos.add(visibleActivityInfo); + break; + case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: + informVisibleActivityChanged(visibleActivityInfo, type); + mVisibleActivityInfos.remove(visibleActivityInfo); + break; + } + } + + private void doRegisterVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull VisibleActivityCallback callback) { + if (mVisibleActivityCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.d(TAG, "doRegisterVisibleActivityCallback: callback has registered"); + } + return; + } + + int preCallbackCount = mVisibleActivityCallbacks.size(); + mVisibleActivityCallbacks.put(callback, executor); + + if (preCallbackCount == 0) { + try { + mSystemService.startListeningVisibleActivityChanged(mToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } else { + for (int i = 0; i < mVisibleActivityInfos.size(); i++) { + final VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfos.get(i); + executor.execute(() -> callback.onVisible(visibleActivityInfo)); + } + } + } + + private void doUnregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { + mVisibleActivityCallbacks.remove(callback); + + if (mVisibleActivityCallbacks.size() == 0) { + mVisibleActivityInfos.clear(); + try { + mSystemService.stopListeningVisibleActivityChanged(mToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + private void informVisibleActivityChanged(VisibleActivityInfo visibleActivityInfo, int type) { + for (Map.Entry<VisibleActivityCallback, Executor> e : + mVisibleActivityCallbacks.entrySet()) { + final Executor executor = e.getValue(); + final VisibleActivityCallback visibleActivityCallback = e.getKey(); + + switch (type) { + case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: + Binder.withCleanCallingIdentity(() -> { + executor.execute( + () -> visibleActivityCallback.onVisible(visibleActivityInfo)); + }); + break; + case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: + Binder.withCleanCallingIdentity(() -> { + executor.execute(() -> visibleActivityCallback.onInvisible( + visibleActivityInfo.getActivityId())); + }); + break; + } + } + } + void ensureWindowCreated() { if (mInitialized) { return; @@ -1926,6 +2043,45 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } /** + * Registers a callback that will be notified when visible activities have been changed. + * + * @param executor The handler to receive the callback. + * @param callback The callback to receive the response. + * + * @throws IllegalStateException if calling this method before onCreate(). + */ + public final void registerVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull VisibleActivityCallback callback) { + if (DEBUG) { + Log.d(TAG, "registerVisibleActivityCallback"); + } + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageOO(MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK, executor, + callback)); + } + + /** + * Unregisters the callback. + * + * @param callback The callback to receive the response. + */ + public final void unregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { + if (DEBUG) { + Log.d(TAG, "unregisterVisibleActivityCallback"); + } + Objects.requireNonNull(callback); + + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageO(MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK, callback)); + } + + /** * Print the Service's state into the given stream. This gets invoked by * {@link VoiceInteractionSessionService} when its Service * {@link android.app.Service#dump} method is called. @@ -1975,6 +2131,17 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } /** + * Callback interface for receiving visible activity changes used for assistant usage. + */ + public interface VisibleActivityCallback { + /** Callback to inform that an activity has become visible. */ + default void onVisible(@NonNull VisibleActivityInfo activityInfo) {} + + /** Callback to inform that a visible activity has gone. */ + default void onInvisible(@NonNull ActivityId activityId) {} + } + + /** * Represents assist state captured when this session was started. * It contains the various assist data objects and a reference to * the source activity. diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index c8a4425409e8..998526209c72 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -272,4 +272,14 @@ interface IVoiceInteractionManagerService { void triggerHardwareRecognitionEventForTest( in SoundTrigger.KeyphraseRecognitionEvent event, in IHotwordRecognitionStatusCallback callback); + + /** + * Starts to listen the status of visible activity. + */ + void startListeningVisibleActivityChanged(in IBinder token); + + /** + * Stops to listen the status of visible activity. + */ + void stopListeningVisibleActivityChanged(in IBinder token); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2b1c69546248..18ed9586d263 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1550,6 +1550,13 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int INDEX_TOTAL_MEMTRACK_GL = 14; private static final int INDEX_LAST = 15; + /** + * Used to notify activity lifecycle events. + */ + @Nullable + volatile ActivityManagerInternal.VoiceInteractionManagerProvider + mVoiceInteractionManagerProvider; + final class UiHandler extends Handler { public UiHandler() { super(com.android.server.UiThread.get().getLooper(), null, true); @@ -1886,6 +1893,14 @@ public class ActivityManagerService extends IActivityManager.Stub return mAppOpsService; } + /** + * Sets the internal voice interaction manager service. + */ + private void setVoiceInteractionManagerProvider( + @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) { + mVoiceInteractionManagerProvider = provider; + } + static class MemBinder extends Binder { ActivityManagerService mActivityManagerService; private final PriorityDump.PriorityDumper mPriorityDumper = @@ -2737,6 +2752,11 @@ public class ActivityManagerService extends IActivityManager.Stub || event == Event.ACTIVITY_DESTROYED)) { contentCaptureService.notifyActivityEvent(userId, activity, event); } + // TODO(b/201234353): Move the logic to client side. + if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED + || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) { + mVoiceInteractionManagerProvider.notifyActivityEventChanged(); + } } /** @@ -16366,6 +16386,12 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManagerService.this.sendIntentSender(target, allowlistToken, code, intent, resolvedType, finishedReceiver, requiredPermission, options); } + + @Override + public void setVoiceInteractionManagerProvider( + @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) { + ActivityManagerService.this.setVoiceInteractionManagerProvider(provider); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 9ea2b7b12ad0..8445ed4884e2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -167,6 +167,16 @@ public class VoiceInteractionManagerService extends SystemService { public void onStart() { publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub); publishLocalService(VoiceInteractionManagerInternal.class, new LocalService()); + mAmInternal.setVoiceInteractionManagerProvider( + new ActivityManagerInternal.VoiceInteractionManagerProvider() { + @Override + public void notifyActivityEventChanged() { + if (DEBUG) { + Slog.d(TAG, "call notifyActivityEventChanged"); + } + mServiceStub.notifyActivityEventChanged(); + } + }); } @Override @@ -386,6 +396,14 @@ public class VoiceInteractionManagerService extends SystemService { return mImpl.supportsLocalVoiceInteraction(); } + void notifyActivityEventChanged() { + synchronized (this) { + if (mImpl == null) return; + + Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked()); + } + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -1109,6 +1127,40 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public void startListeningVisibleActivityChanged(@NonNull IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "startListeningVisibleActivityChanged without running" + + " voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.startListeningVisibleActivityChangedLocked(token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public void stopListeningVisibleActivityChanged(@NonNull IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "stopListeningVisibleActivityChanged without running" + + " voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.stopListeningVisibleActivityChangedLocked(token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + //----------------- Hotword Detection/Validation APIs --------------------------------// @Override diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 558a9ac9298e..52c5b6bbc239 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -414,6 +414,44 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } + public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) { + if (DEBUG) { + Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token); + } + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "startListeningVisibleActivityChangedLocked does not match" + + " active session"); + return; + } + mActiveSession.startListeningVisibleActivityChangedLocked(); + } + + public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) { + if (DEBUG) { + Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token); + } + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "stopListeningVisibleActivityChangedLocked does not match" + + " active session"); + return; + } + mActiveSession.stopListeningVisibleActivityChangedLocked(); + } + + public void notifyActivityEventChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked"); + } + if (mActiveSession == null || !mActiveSession.mShown) { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked not allowed on no session or" + + " hidden session"); + } + return; + } + mActiveSession.notifyActivityEventChangedLocked(); + } + public void updateStateLocked( @NonNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 08e9703124ab..90ccec852e1e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.service.voice.IVoiceInteractionSessionService; +import android.service.voice.VisibleActivityInfo; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionSession; import android.util.Slog; @@ -71,16 +72,20 @@ import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.ActivityAssistInfo; +import com.android.server.wm.ActivityTaskManagerInternal; import java.io.PrintWriter; import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; final class VoiceInteractionSessionConnection implements ServiceConnection, AssistDataRequesterCallbacks { static final String TAG = "VoiceInteractionServiceManager"; + static final boolean DEBUG = false; static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt( System.getProperty("vendor.powerhal.interaction.max", "200")); static final int BOOST_TIMEOUT_MS = 300; @@ -114,6 +119,10 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>(); AssistDataRequester mAssistDataRequester; + private boolean mListeningVisibleActivity; + private final ScheduledExecutorService mScheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(); + private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); private final PowerManagerInternal mPowerManagerInternal; private PowerBoostSetter mSetPowerBoostRunnable; private final Handler mFgHandler; @@ -496,6 +505,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } public void cancelLocked(boolean finishTask) { + mListeningVisibleActivity = false; + mVisibleActivityInfos.clear(); hideLocked(); mCanceled = true; if (mBound) { @@ -569,6 +580,156 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, mPendingShowCallbacks.clear(); } + void startListeningVisibleActivityChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "startListeningVisibleActivityChangedLocked"); + } + mListeningVisibleActivity = true; + mVisibleActivityInfos.clear(); + + mScheduledExecutorService.execute(() -> { + if (DEBUG) { + Slog.d(TAG, "call updateVisibleActivitiesLocked from enable listening"); + } + synchronized (mLock) { + updateVisibleActivitiesLocked(); + } + }); + } + + void stopListeningVisibleActivityChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "stopListeningVisibleActivityChangedLocked"); + } + mListeningVisibleActivity = false; + mVisibleActivityInfos.clear(); + } + + void notifyActivityEventChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked"); + } + if (!mListeningVisibleActivity) { + if (DEBUG) { + Slog.d(TAG, "not enable listening visible activity"); + } + return; + } + mScheduledExecutorService.execute(() -> { + if (DEBUG) { + Slog.d(TAG, "call updateVisibleActivitiesLocked from activity event"); + } + synchronized (mLock) { + updateVisibleActivitiesLocked(); + } + }); + } + + private List<VisibleActivityInfo> getVisibleActivityInfosLocked() { + if (DEBUG) { + Slog.d(TAG, "getVisibleActivityInfosLocked"); + } + List<ActivityAssistInfo> allVisibleActivities = + LocalServices.getService(ActivityTaskManagerInternal.class) + .getTopVisibleActivities(); + if (DEBUG) { + Slog.d(TAG, + "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities); + } + if (allVisibleActivities == null || allVisibleActivities.isEmpty()) { + Slog.w(TAG, "no visible activity"); + return null; + } + final int count = allVisibleActivities.size(); + final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + ActivityAssistInfo info = allVisibleActivities.get(i); + if (DEBUG) { + Slog.d(TAG, " : activityToken=" + info.getActivityToken() + + ", assistToken=" + info.getAssistToken() + + ", taskId=" + info.getTaskId()); + } + visibleActivityInfos.add( + new VisibleActivityInfo(info.getTaskId(), info.getAssistToken())); + } + return visibleActivityInfos; + } + + private void updateVisibleActivitiesLocked() { + if (DEBUG) { + Slog.d(TAG, "updateVisibleActivitiesLocked"); + } + if (mSession == null) { + return; + } + if (!mShown || !mListeningVisibleActivity || mCanceled) { + return; + } + final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked(); + + if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) { + updateVisibleActivitiesChangedLocked(mVisibleActivityInfos, + VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + mVisibleActivityInfos.clear(); + return; + } + if (mVisibleActivityInfos.isEmpty()) { + updateVisibleActivitiesChangedLocked(newVisibleActivityInfos, + VisibleActivityInfo.TYPE_ACTIVITY_ADDED); + mVisibleActivityInfos.addAll(newVisibleActivityInfos); + return; + } + + final List<VisibleActivityInfo> addedActivities = new ArrayList<>(); + final List<VisibleActivityInfo> removedActivities = new ArrayList<>(); + + removedActivities.addAll(mVisibleActivityInfos); + for (int i = 0; i < newVisibleActivityInfos.size(); i++) { + final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i); + if (!removedActivities.isEmpty() && removedActivities.contains( + candidateVisibleActivityInfo)) { + removedActivities.remove(candidateVisibleActivityInfo); + } else { + addedActivities.add(candidateVisibleActivityInfo); + } + } + + if (!addedActivities.isEmpty()) { + updateVisibleActivitiesChangedLocked(addedActivities, + VisibleActivityInfo.TYPE_ACTIVITY_ADDED); + } + if (!removedActivities.isEmpty()) { + updateVisibleActivitiesChangedLocked(removedActivities, + VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + } + + mVisibleActivityInfos.clear(); + mVisibleActivityInfos.addAll(newVisibleActivityInfos); + } + + private void updateVisibleActivitiesChangedLocked( + List<VisibleActivityInfo> visibleActivityInfos, int type) { + if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) { + return; + } + if (mSession == null) { + return; + } + try { + for (int i = 0; i < visibleActivityInfos.size(); i++) { + mSession.updateVisibleActivityInfo(visibleActivityInfos.get(i), type); + } + } catch (RemoteException e) { + if (DEBUG) { + Slog.w(TAG, "updateVisibleActivitiesChangedLocked RemoteException : " + e); + } + } + if (DEBUG) { + Slog.d(TAG, "updateVisibleActivitiesChangedLocked type=" + type + ", count=" + + visibleActivityInfos.size()); + } + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { |