Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9831f53b authored by Andrii Kulian's avatar Andrii Kulian Committed by Android (Google) Code Review
Browse files

Merge changes from topic "mt_bubble_controller_death_handlers" into main

* changes:
  Handle WM Shell crash in MultitaskingController
  Notify app clients about managed Bubbles removals
parents 12a5a03a 01bc6c0a
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.window;
import android.os.Bundle;
import android.os.IBinder;
import android.content.Intent;
import android.window.IMultitaskingControllerCallback;
import android.window.IMultitaskingDelegate;

/**
@@ -36,14 +37,15 @@ import android.window.IMultitaskingDelegate;
 */
interface IMultitaskingController {
    /**
     * Method used by WMShell to register itself as a delegate that can respond to the app requests.
     * Method used by WMShell to set itself as the delegate that can respond to the app requests.
     * @return a callback used to notify the client about the changes in the managed windows.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)")
    oneway void registerMultitaskingDelegate(in IMultitaskingDelegate delegate);
    IMultitaskingControllerCallback setMultitaskingDelegate(in IMultitaskingDelegate delegate);

    /**
     * Returns an instance of an interface for use by applications to make requests to the system.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.REQUEST_SYSTEM_MULTITASKING_CONTROLS)")
    @nullable IMultitaskingDelegate getClientInterface();
    @nullable IMultitaskingDelegate getClientInterface(in IMultitaskingControllerCallback callback);
}
+29 −0
Original line number Diff line number Diff line
/**
 * Copyright (c) 2025 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 android.os.IBinder;
import android.content.Intent;

/**
 * System private callback for notifying the registered clients about the updates related to
 * multitasking features they requested to control.
 * @hide
 */
interface IMultitaskingControllerCallback {
    oneway void onBubbleRemoved(in IBinder token);
}
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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 androidx.window.extensions.bubble;

import android.os.IBinder;

/**
 * Public interface used to notify applications about the changes to the bubble containers they
 * manage.
 */
public interface BubbleContainerCallback {
    /** Notifies about removal of a Bubble that was previously opened by the client. */
    void onBubbleRemoved(IBinder token);
}
+74 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package androidx.window.extensions.bubble;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.RequiresPermission;
import android.app.ActivityTaskManager;
import android.content.Intent;
@@ -24,12 +25,16 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.window.IMultitaskingController;
import android.window.IMultitaskingControllerCallback;
import android.window.IMultitaskingDelegate;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * The interface that apps use to request control over Bubbles - special windowing feature that
@@ -41,13 +46,16 @@ public class BubbleContainerManager {

    private static BubbleContainerManager sInstance;

    // Interface used to communicate with the controller in the server
    private IMultitaskingDelegate mControllerInterface;

    // Callback used to update the client about changes from the controller in the server
    @NonNull
    private final IMultitaskingDelegate mClientInterface;
    private final ControllerCallback mControllerCallback = new ControllerCallback();

    private BubbleContainerManager(@NonNull IMultitaskingDelegate clientInterface) {
        Objects.requireNonNull(clientInterface);
        mClientInterface = clientInterface;
    }
    // Callback into the application code
    @NonNull
    private final List<CallbackRecord> mAppCallbacks = new ArrayList<>();

    /**
     * Obtain an instance of the class to make requests related to Bubbles system multi-tasking
@@ -66,10 +74,14 @@ public class BubbleContainerManager {
            return sInstance;
        }
        try {
            IMultitaskingController controller = ActivityTaskManager.getService()
            final IMultitaskingController controller = ActivityTaskManager.getService()
                    .getWindowOrganizerController().getMultitaskingController();
            IMultitaskingDelegate clientInterface = controller.getClientInterface();
            sInstance = new BubbleContainerManager(clientInterface);
            final BubbleContainerManager manager = new BubbleContainerManager();
            final IMultitaskingDelegate controllerInterface = controller.getClientInterface(
                    manager.mControllerCallback);
            Objects.requireNonNull(controllerInterface);
            sInstance = manager;
            sInstance.mControllerInterface = controllerInterface;
            return sInstance;
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception getting an instance of BubbleContainerManager",
@@ -89,13 +101,12 @@ public class BubbleContainerManager {
     */
    public void createBubble(IBinder token, Intent intent, boolean collapsed) {
        try {
            mClientInterface.createBubble(token, intent, collapsed);
            mControllerInterface.createBubble(token, intent, collapsed);
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception creating a Bubble", new RuntimeException(e));
        }
    }

    // TODO(b/407149510): Handle the case when the user removes the Bubble after it was created.
    /**
     * Update the state of an existing Bubble to either collapse or expand it.
     * @param token a token uniquely identifying a bubble.
@@ -103,7 +114,7 @@ public class BubbleContainerManager {
     */
    public void updateBubbleState(IBinder token, boolean collapsed) {
        try {
            mClientInterface.updateBubbleState(token, collapsed);
            mControllerInterface.updateBubbleState(token, collapsed);
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception updating Bubble state", new RuntimeException(e));
        }
@@ -116,7 +127,7 @@ public class BubbleContainerManager {
     */
    public void updateBubbleMessage(IBinder token, String message) {
        try {
            mClientInterface.updateBubbleMessage(token, message);
            mControllerInterface.updateBubbleMessage(token, message);
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception updating Bubble message", new RuntimeException(e));
        }
@@ -128,9 +139,59 @@ public class BubbleContainerManager {
     */
    public void removeBubble(IBinder token) {
        try {
            mClientInterface.removeBubble(token);
            mControllerInterface.removeBubble(token);
        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception removing Bubble", new RuntimeException(e));
        }
    }

    /**
     * Registers a callback to get notified about changes to the managed bubbles.
     */
    public void registerBubbleContainerCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull BubbleContainerCallback callback) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        for (CallbackRecord record : mAppCallbacks) {
            if (record.mCallback.equals(callback)) {
                throw new IllegalArgumentException("Callback already registered");
            }
        }
        mAppCallbacks.add(new CallbackRecord(executor, callback));
    }

    /**
     * Unregisters a callback that was previously registered.
     * @see #registerBubbleContainerCallback(Executor, BubbleContainerCallback)
     */
    public void unregisterBubbleContainerCallback(@NonNull BubbleContainerCallback callback) {
        for (CallbackRecord record : mAppCallbacks) {
            if (record.mCallback.equals(callback)) {
                mAppCallbacks.remove(record);
                return;
            }
        }
        Log.e(TAG, "Didn't find a matching callback to remove");
    }

    private static class CallbackRecord {
        final Executor mExecutor;
        final BubbleContainerCallback mCallback;

        CallbackRecord(@NonNull Executor executor, @NonNull BubbleContainerCallback callback) {
            mExecutor = executor;
            mCallback = callback;
        }
    }

    private class ControllerCallback extends IMultitaskingControllerCallback.Stub {
        @Override
        public void onBubbleRemoved(IBinder token) {
            for (CallbackRecord callbackRecord : mAppCallbacks) {
                callbackRecord.mExecutor.execute(() -> {
                    callbackRecord.mCallback.onBubbleRemoved(token);
                });
            }
        }
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.IMultitaskingController;
import android.window.IMultitaskingControllerCallback;
import android.window.ScreenCapture;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.TransitionInfo;
@@ -557,8 +558,10 @@ public class BubbleController implements ConfigurationChangeListener,
                        this, mBubbleData, mCurrentUserId);
                final IMultitaskingController mtController = ActivityTaskManager.getService()
                        .getWindowOrganizerController().getMultitaskingController();
                mtController.registerMultitaskingDelegate(delegate);
                final IMultitaskingControllerCallback callback =
                        mtController.setMultitaskingDelegate(delegate);
                mBubbleMultitaskingDelegate = delegate;
                mBubbleMultitaskingDelegate.setControllerCallback(callback);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to register Bubble multitasking delegate.", e);
            }
@@ -2431,6 +2434,9 @@ public class BubbleController implements ConfigurationChangeListener,
                @Bubbles.DismissReason final int reason = removed.second;

                mBubbleViewCallback.removeBubble(bubble);
                if (bubble.getClientToken() != null && mBubbleMultitaskingDelegate != null) {
                    mBubbleMultitaskingDelegate.onBubbleRemoved(bubble.getClientToken(), reason);
                }

                // Leave the notification in place if we're dismissing due to user switching, or
                // because DND is suppressing the bubble. In both of those cases, we need to be able
Loading