/* * 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.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ObservableServiceConnection; import com.android.internal.util.PersistentServiceConnection; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Handles the service connection to {@link IDreamOverlay} * * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class DreamOverlayConnectionHandler { private static final String TAG = "DreamOverlayConnection"; private static final int MSG_ADD_CONSUMER = 1; private static final int MSG_REMOVE_CONSUMER = 2; private static final int MSG_OVERLAY_CLIENT_READY = 3; private final Handler mHandler; private final PersistentServiceConnection mConnection; // Retrieved Client private IDreamOverlayClient mClient; // A list of pending requests to execute on the overlay. private final List> mConsumers = new ArrayList<>(); private final OverlayConnectionCallback mCallback; DreamOverlayConnectionHandler( Context context, Looper looper, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs) { this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts, baseReconnectDelayMs, new Injector()); } @VisibleForTesting public DreamOverlayConnectionHandler( Context context, Looper looper, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs, Injector injector) { mCallback = new OverlayConnectionCallback(); mHandler = new Handler(looper, new OverlayHandlerCallback()); mConnection = injector.buildConnection( context, mHandler, serviceIntent, minConnectionDurationMs, maxReconnectAttempts, baseReconnectDelayMs ); } /** * Bind to the overlay service. If binding fails, we automatically call unbind to clean * up resources. * * @return true if binding was successful, false otherwise. */ public boolean bind() { mConnection.addCallback(mCallback); final boolean success = mConnection.bind(); if (!success) { unbind(); } return success; } /** * Unbind from the overlay service, clearing any pending callbacks. */ public void unbind() { mConnection.removeCallback(mCallback); // Remove any pending messages. mHandler.removeCallbacksAndMessages(null); mClient = null; mConsumers.clear(); mConnection.unbind(); } /** * Adds a consumer to run once the overlay service has connected. If the overlay service * disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless * removed. * * @param consumer The consumer to run. This consumer is always executed asynchronously. */ public void addConsumer(Consumer consumer) { final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer); mHandler.sendMessage(msg); } /** * Removes the consumer, preventing this consumer from being called again. * * @param consumer The consumer to remove. */ public void removeConsumer(Consumer consumer) { final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer); mHandler.sendMessage(msg); // Clear any pending messages to add this consumer mHandler.removeMessages(MSG_ADD_CONSUMER, consumer); } private final class OverlayHandlerCallback implements Handler.Callback { @Override public boolean handleMessage(@NonNull Message msg) { switch (msg.what) { case MSG_OVERLAY_CLIENT_READY: onOverlayClientReady((IDreamOverlayClient) msg.obj); break; case MSG_ADD_CONSUMER: onAddConsumer((Consumer) msg.obj); break; case MSG_REMOVE_CONSUMER: onRemoveConsumer((Consumer) msg.obj); break; } return true; } } private void onOverlayClientReady(IDreamOverlayClient client) { mClient = client; for (Consumer consumer : mConsumers) { consumer.accept(mClient); } } private void onAddConsumer(Consumer consumer) { if (mClient != null) { consumer.accept(mClient); } mConsumers.add(consumer); } private void onRemoveConsumer(Consumer consumer) { mConsumers.remove(consumer); } private final class OverlayConnectionCallback implements ObservableServiceConnection.Callback { private final IDreamOverlayClientCallback mClientCallback = new IDreamOverlayClientCallback.Stub() { @Override public void onDreamOverlayClient(IDreamOverlayClient client) { final Message msg = mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client); mHandler.sendMessage(msg); } }; @Override public void onConnected( ObservableServiceConnection connection, IDreamOverlay service) { try { service.getClient(mClientCallback); } catch (RemoteException e) { Log.e(TAG, "could not get DreamOverlayClient", e); } } @Override public void onDisconnected(ObservableServiceConnection connection, int reason) { mClient = null; // Cancel any pending messages about the overlay being ready, since it is no // longer ready. mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY); } } /** * Injector for testing */ @VisibleForTesting public static class Injector { /** * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden * in tests with a fake clock. */ public PersistentServiceConnection buildConnection( Context context, Handler handler, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs) { final Executor executor = handler::post; final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; return new PersistentServiceConnection<>( context, executor, handler, IDreamOverlay.Stub::asInterface, serviceIntent, flags, minConnectionDurationMs, maxReconnectAttempts, baseReconnectDelayMs ); } } }