Implement OnBackInvokedDispatcher

Bug:209867448
Test: mp core services sysuig. atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest

Change-Id: Ib74250e93926854f07ebba6b9f680040aa98f9a4
This commit is contained in:
Shan Huang
2022-01-11 03:17:12 +00:00
parent da433c60c1
commit 54b9c9d66e
6 changed files with 362 additions and 6 deletions

View File

@@ -37,6 +37,7 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
import java.util.List;
@@ -328,4 +329,12 @@ interface IWindowSession {
*/
oneway void generateDisplayHash(IWindow window, in Rect boundsInWindow,
in String hashAlgorithm, in RemoteCallback callback);
/**
* Sets the {@link IOnBackInvokedCallback} to be invoked for a window when back is triggered.
*
* @param window The token for the window to set the callback to.
* @param callback The {@link IOnBackInvokedCallback} to set.
*/
oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);
}

View File

@@ -28,6 +28,7 @@ import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
import java.util.HashMap;
import java.util.Objects;
@@ -495,6 +496,10 @@ public class WindowlessWindowManager implements IWindowSession {
RemoteCallback callback) {
}
@Override
public void setOnBackInvokedCallback(IWindow iWindow,
IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { }
@Override
public boolean dropForAccessibility(IWindow window, int x, int y) {
return false;

View File

@@ -0,0 +1,55 @@
/*
* 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.window;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
* and called from back handling process when back is invoked.
*
* @hide
*/
oneway interface IOnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted()}.
*/
void onBackStarted();
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
*
* @param touchX Absolute X location of the touch point.
* @param touchY Absolute Y location of the touch point.
* @param progress Value between 0 and 1 on how far along the back gesture is.
*/
void onBackProgressed(int touchX, int touchY, float progress);
/**
* Called when a back gesture or back button press has been cancelled.
* Wraps {@link OnBackInvokedCallback#onBackCancelled()}.
*/
void onBackCancelled();
/**
* Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed.
* Wraps {@link OnBackInvokedCallback#onBackInvoked()}.
*/
void onBackInvoked();
}

View File

@@ -17,11 +17,19 @@
package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.OnBackInvokedCallback;
import android.view.OnBackInvokedDispatcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeMap;
/**
* Provides window based implementation of {@link OnBackInvokedDispatcher}.
*
@@ -39,6 +47,18 @@ import android.view.OnBackInvokedDispatcher;
public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
private IWindowSession mWindowSession;
private IWindow mWindow;
private static final String TAG = "WindowOnBackDispatcher";
private static final boolean DEBUG = false;
/** The currently most prioritized callback. */
@Nullable
private OnBackInvokedCallbackWrapper mTopCallback;
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
/**
* Sends the pending top callback (if one exists) to WM when the view root
@@ -47,7 +67,9 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
mWindowSession = windowSession;
mWindow = window;
// TODO(b/209867448): Send the top callback to WM (if one exists).
if (mTopCallback != null) {
setTopOnBackInvokedCallback(mTopCallback);
}
}
/** Detaches the dispatcher instance from its window. */
@@ -56,20 +78,124 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
mWindowSession = null;
}
// TODO: Take an Executor for the callback to run on.
@Override
public void registerOnBackInvokedCallback(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
// TODO(b/209867448): To be implemented.
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
// If callback has already been added, remove it and re-add it.
if (mAllCallbacks.containsKey(callback)) {
if (DEBUG) {
Log.i(TAG, "Callback already added. Removing and re-adding it.");
}
Integer prevPriority = mAllCallbacks.get(callback);
mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
}
callbacks.add(callback);
mAllCallbacks.put(callback, priority);
if (mTopCallback == null || (mTopCallback.getCallback() != callback
&& mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) {
setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority));
}
}
@Override
public void unregisterOnBackInvokedCallback(
@NonNull OnBackInvokedCallback callback) {
// TODO(b/209867448): To be implemented.
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
if (!mAllCallbacks.containsKey(callback)) {
if (DEBUG) {
Log.i(TAG, "Callback not found. returning...");
}
return;
}
Integer priority = mAllCallbacks.get(callback);
mOnBackInvokedCallbacks.get(priority).remove(callback);
mAllCallbacks.remove(callback);
if (mTopCallback != null && mTopCallback.getCallback() == callback) {
findAndSetTopOnBackInvokedCallback();
}
}
/** Clears all registered callbacks on the instance. */
public void clear() {
// TODO(b/209867448): To be implemented.
mAllCallbacks.clear();
mTopCallback = null;
mOnBackInvokedCallbacks.clear();
}
/**
* Iterates through all callbacks to find the most prioritized one and pushes it to
* window manager.
*/
private void findAndSetTopOnBackInvokedCallback() {
if (mAllCallbacks.isEmpty()) {
setTopOnBackInvokedCallback(null);
return;
}
for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
if (!callbacks.isEmpty()) {
OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper(
callbacks.get(callbacks.size() - 1), priority);
setTopOnBackInvokedCallback(callback);
return;
}
}
setTopOnBackInvokedCallback(null);
}
// Pushes the top priority callback to window manager.
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) {
mTopCallback = callback;
if (mWindowSession == null || mWindow == null) {
return;
}
try {
mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback);
} catch (RemoteException e) {
Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);
}
}
private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final OnBackInvokedCallback mCallback;
private final @Priority int mPriority;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
mCallback = callback;
mPriority = priority;
}
@NonNull
public OnBackInvokedCallback getCallback() {
return mCallback;
}
@Override
public void onBackStarted() throws RemoteException {
Handler.getMain().post(() -> mCallback.onBackStarted());
}
@Override
public void onBackProgressed(int touchX, int touchY, float progress)
throws RemoteException {
Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress));
}
@Override
public void onBackCancelled() throws RemoteException {
Handler.getMain().post(() -> mCallback.onBackCancelled());
}
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> mCallback.onBackInvoked());
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.window;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.OnBackInvokedCallback;
import android.view.OnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link WindowOnBackInvokedDispatcherTest}
*
* <p>Build/Install/Run:
* atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class WindowOnBackInvokedDispatcherTest {
@Mock
private IWindowSession mWindowSession;
@Mock
private IWindow mWindow;
private WindowOnBackInvokedDispatcher mDispatcher;
@Mock
private OnBackInvokedCallback mCallback1;
@Mock
private OnBackInvokedCallback mCallback2;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mDispatcher = new WindowOnBackInvokedDispatcher();
mDispatcher.attachToWindow(mWindowSession, mWindow);
}
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Test
public void propagatesTopCallback_samePriority() throws RemoteException {
ArgumentCaptor<IOnBackInvokedCallback> captor =
ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
mDispatcher.registerOnBackInvokedCallback(
mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
verify(mWindowSession, times(2))
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
captor.getAllValues().get(0).onBackStarted();
waitForIdle();
verify(mCallback1).onBackStarted();
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).onBackStarted();
waitForIdle();
verify(mCallback2).onBackStarted();
verifyNoMoreInteractions(mCallback1);
}
@Test
public void propagatesTopCallback_differentPriority() throws RemoteException {
ArgumentCaptor<IOnBackInvokedCallback> captor =
ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
mDispatcher.registerOnBackInvokedCallback(
mCallback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
verify(mWindowSession)
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
verifyNoMoreInteractions(mWindowSession);
captor.getValue().onBackStarted();
waitForIdle();
verify(mCallback1).onBackStarted();
}
@Test
public void propagatesTopCallback_withRemoval() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(
mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
reset(mWindowSession);
mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
verifyZeroInteractions(mWindowSession);
mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
verify(mWindowSession).setOnBackInvokedCallback(Mockito.eq(mWindow), isNull());
}
@Test
public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException {
ArgumentCaptor<IOnBackInvokedCallback> captor =
ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
mDispatcher.registerOnBackInvokedCallback(mCallback1,
OnBackInvokedDispatcher.PRIORITY_OVERLAY);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
mDispatcher.registerOnBackInvokedCallback(
mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
reset(mWindowSession);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
verify(mWindowSession)
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
captor.getValue().onBackStarted();
waitForIdle();
verify(mCallback2).onBackStarted();
}
}

View File

@@ -73,6 +73,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -863,4 +864,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void setOnBackInvokedCallback(IWindow iWindow,
IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException {
// TODO: Set the callback to the WindowState of the window.
}
}