Merge "Implement OnBackInvokedDispatcher"
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -333,4 +334,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -500,6 +501,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;
|
||||
|
||||
55
core/java/android/window/IOnBackInvokedCallback.aidl
Normal file
55
core/java/android/window/IOnBackInvokedCallback.aidl
Normal 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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -873,4 +874,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.
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user