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

Commit c7b7735c authored by Bryce Lee's avatar Bryce Lee
Browse files

Interact with DreamOverlay through distinct clients.

DreamOverlay clients currently interact with the service endpoint
through a single binder interface. From the DreamOverlay perspective,
it is impossible to distinguish between clients. This makes it
difficult to understand if requests are coming from the currently
active client. This surfaces in the product as two dreams that
interfere with each other. For example, an exiting dream might send
the dream ending signal after the current dream has started.

This changelist introduces a DreamOverlay client binder interface,
which is minted by the DreamOverlayService on demand. The existing
actions on the overlay have been moved to this client. The
DreamOverlayService maintains a reference to the active client so
that it can verify which one can act upon the overlay.

Test: atest DreamOverlayServiceTest
Fixes: 266703170
Change-Id: I23e2e2adea626361bb9d2e6969341aade33b1a23
parent 032f978c
Loading
Loading
Loading
Loading
+93 −22
Original line number Original line Diff line number Diff line
@@ -36,39 +36,101 @@ import android.view.WindowManager;
public abstract class DreamOverlayService extends Service {
public abstract class DreamOverlayService extends Service {
    private static final String TAG = "DreamOverlayService";
    private static final String TAG = "DreamOverlayService";
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = false;

    // The last client that started dreaming and hasn't ended
    private OverlayClient mCurrentClient;

    // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
    // requests to the {@link DreamOverlayService}
    private static class OverlayClient extends IDreamOverlayClient.Stub {
        private final DreamOverlayService mService;
        private boolean mShowComplications;
        private boolean mShowComplications;
        private ComponentName mDreamComponent;
        private ComponentName mDreamComponent;
        IDreamOverlayCallback mDreamOverlayCallback;

        OverlayClient(DreamOverlayService service) {
            mService = service;
        }


    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
        @Override
        @Override
        public void startDream(WindowManager.LayoutParams layoutParams,
        public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
                IDreamOverlayCallback callback, String dreamComponent,
                String dreamComponent, boolean shouldShowComplications) throws RemoteException {
                boolean shouldShowComplications) {
            mDreamOverlayCallback = callback;
            mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
            mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
            mShowComplications = shouldShowComplications;
            mShowComplications = shouldShowComplications;
            onStartDream(layoutParams);
            mDreamOverlayCallback = callback;
            mService.startDream(this, params);
        }



        @Override
        public void wakeUp() {
            mService.wakeUp(this, () -> {
                try {
                    mDreamOverlayCallback.onWakeUpComplete();
                } catch (RemoteException e) {
                    Log.e(TAG, "Could not notify dream of wakeUp", e);
                }
            });
        }
        }


        @Override
        @Override
        public void endDream() {
        public void endDream() {
            mService.endDream(this);
        }

        private void onExitRequested() {
            try {
                mDreamOverlayCallback.onExitRequested();
            } catch (RemoteException e) {
                Log.e(TAG, "Could not request exit:" + e);
            }
        }

        private boolean shouldShowComplications() {
            return mShowComplications;
        }

        private ComponentName getComponent() {
            return mDreamComponent;
        }
    }

    private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
        endDream(mCurrentClient);
        mCurrentClient = client;
        onStartDream(params);
    }

    private void endDream(OverlayClient client) {
        if (client == null || client != mCurrentClient) {
            return;
        }

        onEndDream();
        onEndDream();
        mCurrentClient = null;
    }

    private void wakeUp(OverlayClient client, Runnable callback) {
        if (mCurrentClient != client) {
            return;
        }
        }


        onWakeUp(callback);
    }

    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
        @Override
        @Override
        public void wakeUp() {
        public void getClient(IDreamOverlayClientCallback callback) {
            onWakeUp(() -> {
            try {
            try {
                    mDreamOverlayCallback.onWakeUpComplete();
                callback.onDreamOverlayClient(
                        new OverlayClient(DreamOverlayService.this));
            } catch (RemoteException e) {
            } catch (RemoteException e) {
                    Log.e(TAG, "Could not notify dream of wakeUp:" + e);
                Log.e(TAG, "could not send client to callback", e);
            }
            }
            });
        }
        }
    };
    };


    IDreamOverlayCallback mDreamOverlayCallback;

    public DreamOverlayService() {
    public DreamOverlayService() {
    }
    }


@@ -110,18 +172,23 @@ public abstract class DreamOverlayService extends Service {
     * This method is invoked to request the dream exit.
     * This method is invoked to request the dream exit.
     */
     */
    public final void requestExit() {
    public final void requestExit() {
        try {
        if (mCurrentClient == null) {
            mDreamOverlayCallback.onExitRequested();
            throw new IllegalStateException("requested exit with no dream present");
        } catch (RemoteException e) {
            Log.e(TAG, "Could not request exit:" + e);
        }
        }

        mCurrentClient.onExitRequested();
    }
    }


    /**
    /**
     * Returns whether to show complications on the dream overlay.
     * Returns whether to show complications on the dream overlay.
     */
     */
    public final boolean shouldShowComplications() {
    public final boolean shouldShowComplications() {
        return mShowComplications;
        if (mCurrentClient == null) {
            throw new IllegalStateException(
                    "requested if should show complication when no dream active");
        }

        return mCurrentClient.shouldShowComplications();
    }
    }


    /**
    /**
@@ -129,6 +196,10 @@ public abstract class DreamOverlayService extends Service {
     * @hide
     * @hide
     */
     */
    public final ComponentName getDreamComponent() {
    public final ComponentName getDreamComponent() {
        return mDreamComponent;
        if (mCurrentClient == null) {
            throw new IllegalStateException("requested dream component when no dream active");
        }

        return mCurrentClient.getComponent();
    }
    }
}
}
+26 −12
Original line number Original line Diff line number Diff line
@@ -248,25 +248,39 @@ public class DreamService extends Service implements Window.Callback {
    private OverlayConnection mOverlayConnection;
    private OverlayConnection mOverlayConnection;


    private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
    private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
        // Overlay set during onBind.
        // Retrieved Client
        private IDreamOverlay mOverlay;
        private IDreamOverlayClient mClient;

        // A list of pending requests to execute on the overlay.
        // A list of pending requests to execute on the overlay.
        private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
        private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();

        private final IDreamOverlayClientCallback mClientCallback =
                new IDreamOverlayClientCallback.Stub() {
            @Override
            public void onDreamOverlayClient(IDreamOverlayClient client) {
                mClient = client;

                for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
                    consumer.accept(mClient);
                }
            }
        };


        private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
        private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
            @Override
            @Override
            public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
            public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
                    IDreamOverlay service) {
                    IDreamOverlay service) {
                mOverlay = service;
                try {
                for (Consumer<IDreamOverlay> consumer : mConsumers) {
                    service.getClient(mClientCallback);
                    consumer.accept(mOverlay);
                } catch (RemoteException e) {
                    Log.e(TAG, "could not get DreamOverlayClient", e);
                }
                }
            }
            }


            @Override
            @Override
            public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
            public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
                    int reason) {
                    int reason) {
                mOverlay = null;
                mClient = null;
            }
            }
        };
        };


@@ -296,16 +310,16 @@ public class DreamService extends Service implements Window.Callback {
            super.unbind();
            super.unbind();
        }
        }


        public void addConsumer(Consumer<IDreamOverlay> consumer) {
        public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
            execute(() -> {
            execute(() -> {
                mConsumers.add(consumer);
                mConsumers.add(consumer);
                if (mOverlay != null) {
                if (mClient != null) {
                    consumer.accept(mOverlay);
                    consumer.accept(mClient);
                }
                }
            });
            });
        }
        }


        public void removeConsumer(Consumer<IDreamOverlay> consumer) {
        public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
            execute(() -> mConsumers.remove(consumer));
            execute(() -> mConsumers.remove(consumer));
        }
        }


@@ -1365,7 +1379,7 @@ public class DreamService extends Service implements Window.Callback {


        mWindow.getDecorView().addOnAttachStateChangeListener(
        mWindow.getDecorView().addOnAttachStateChangeListener(
                new View.OnAttachStateChangeListener() {
                new View.OnAttachStateChangeListener() {
                    private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
                    private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer;


                    @Override
                    @Override
                    public void onViewAttachedToWindow(View v) {
                    public void onViewAttachedToWindow(View v) {
+3 −17
Original line number Original line Diff line number Diff line
@@ -16,8 +16,7 @@


package android.service.dreams;
package android.service.dreams;


import android.service.dreams.IDreamOverlayCallback;
import android.service.dreams.IDreamOverlayClientCallback;
import android.view.WindowManager.LayoutParams;


/**
/**
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,20 +27,7 @@ import android.view.WindowManager.LayoutParams;
*/
*/
interface IDreamOverlay {
interface IDreamOverlay {
    /**
    /**
    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
    * Retrieves a client the caller can use to interact with the dream overlay.
                    token of the Dream Activity.
    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
    *                dream.
    * @param dreamComponent The component name of the dream service requesting overlay.
    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
    *                and weather.
    */
    */
    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
    void getClient(in IDreamOverlayClientCallback callback);
        in String dreamComponent, in boolean shouldShowComplications);

    /** Called when the dream is waking, to do any exit animations */
    void wakeUp();

    /** Called when the dream has ended. */
    void endDream();
}
}
+45 −0
Original line number Original line Diff line number Diff line
/**
 * Copyright (c) 2023, 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.dreams;

import android.service.dreams.IDreamOverlayCallback;
import android.view.WindowManager.LayoutParams;

/**
* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
*
* @hide
*/
interface IDreamOverlayClient {
    /**
    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
                    token of the Dream Activity.
    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
    *                dream.
    * @param dreamComponent The component name of the dream service requesting overlay.
    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
    *                and weather.
    */
    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
        in String dreamComponent, in boolean shouldShowComplications);

    /** Called when the dream is waking, to do any exit animations */
    void wakeUp();

    /** Called when the dream has ended. */
    void endDream();
}
+30 −0
Original line number Original line Diff line number Diff line
/**
 * Copyright (c) 2023, 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.dreams;

import android.service.dreams.IDreamOverlayClient;

/**
* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
* @hide
*/
interface IDreamOverlayClientCallback {
    /**
    * Called with a unique {@link IDreamOverlayClient}.
    */
    void onDreamOverlayClient(in IDreamOverlayClient client);
}
Loading