• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.dreams;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.ObservableServiceConnection;
30 import com.android.internal.util.PersistentServiceConnection;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.concurrent.Executor;
35 import java.util.function.Consumer;
36 
37 /**
38  * Handles the service connection to {@link IDreamOverlay}
39  *
40  * @hide
41  */
42 @VisibleForTesting
43 public final class DreamOverlayConnectionHandler {
44     private static final String TAG = "DreamOverlayConnection";
45 
46     private static final int MSG_ADD_CONSUMER = 1;
47     private static final int MSG_REMOVE_CONSUMER = 2;
48     private static final int MSG_OVERLAY_CLIENT_READY = 3;
49 
50     private final Handler mHandler;
51     private final PersistentServiceConnection<IDreamOverlay> mConnection;
52     // Retrieved Client
53     private IDreamOverlayClient mClient;
54     // A list of pending requests to execute on the overlay.
55     private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
56     private final OverlayConnectionCallback mCallback;
57 
DreamOverlayConnectionHandler( Context context, Looper looper, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs)58     DreamOverlayConnectionHandler(
59             Context context,
60             Looper looper,
61             Intent serviceIntent,
62             int minConnectionDurationMs,
63             int maxReconnectAttempts,
64             int baseReconnectDelayMs) {
65         this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts,
66                 baseReconnectDelayMs, new Injector());
67     }
68 
69     @VisibleForTesting
DreamOverlayConnectionHandler( Context context, Looper looper, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs, Injector injector)70     public DreamOverlayConnectionHandler(
71             Context context,
72             Looper looper,
73             Intent serviceIntent,
74             int minConnectionDurationMs,
75             int maxReconnectAttempts,
76             int baseReconnectDelayMs,
77             Injector injector) {
78         mCallback = new OverlayConnectionCallback();
79         mHandler = new Handler(looper, new OverlayHandlerCallback());
80         mConnection = injector.buildConnection(
81                 context,
82                 mHandler,
83                 serviceIntent,
84                 minConnectionDurationMs,
85                 maxReconnectAttempts,
86                 baseReconnectDelayMs
87         );
88     }
89 
90     /**
91      * Bind to the overlay service. If binding fails, we automatically call unbind to clean
92      * up resources.
93      *
94      * @return true if binding was successful, false otherwise.
95      */
bind()96     public boolean bind() {
97         mConnection.addCallback(mCallback);
98         final boolean success = mConnection.bind();
99         if (!success) {
100             unbind();
101         }
102         return success;
103     }
104 
105     /**
106      * Unbind from the overlay service, clearing any pending callbacks.
107      */
unbind()108     public void unbind() {
109         mConnection.removeCallback(mCallback);
110         // Remove any pending messages.
111         mHandler.removeCallbacksAndMessages(null);
112         mClient = null;
113         mConsumers.clear();
114         mConnection.unbind();
115     }
116 
117     /**
118      * Adds a consumer to run once the overlay service has connected. If the overlay service
119      * disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless
120      * removed.
121      *
122      * @param consumer The consumer to run. This consumer is always executed asynchronously.
123      */
addConsumer(Consumer<IDreamOverlayClient> consumer)124     public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
125         final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer);
126         mHandler.sendMessage(msg);
127     }
128 
129     /**
130      * Removes the consumer, preventing this consumer from being called again.
131      *
132      * @param consumer The consumer to remove.
133      */
removeConsumer(Consumer<IDreamOverlayClient> consumer)134     public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
135         final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer);
136         mHandler.sendMessage(msg);
137         // Clear any pending messages to add this consumer
138         mHandler.removeMessages(MSG_ADD_CONSUMER, consumer);
139     }
140 
141     private final class OverlayHandlerCallback implements Handler.Callback {
142         @Override
handleMessage(@onNull Message msg)143         public boolean handleMessage(@NonNull Message msg) {
144             switch (msg.what) {
145                 case MSG_OVERLAY_CLIENT_READY:
146                     onOverlayClientReady((IDreamOverlayClient) msg.obj);
147                     break;
148                 case MSG_ADD_CONSUMER:
149                     onAddConsumer((Consumer<IDreamOverlayClient>) msg.obj);
150                     break;
151                 case MSG_REMOVE_CONSUMER:
152                     onRemoveConsumer((Consumer<IDreamOverlayClient>) msg.obj);
153                     break;
154             }
155             return true;
156         }
157     }
158 
onOverlayClientReady(IDreamOverlayClient client)159     private void onOverlayClientReady(IDreamOverlayClient client) {
160         mClient = client;
161         for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
162             consumer.accept(mClient);
163         }
164     }
165 
onAddConsumer(Consumer<IDreamOverlayClient> consumer)166     private void onAddConsumer(Consumer<IDreamOverlayClient> consumer) {
167         if (mClient != null) {
168             consumer.accept(mClient);
169         }
170         mConsumers.add(consumer);
171     }
172 
onRemoveConsumer(Consumer<IDreamOverlayClient> consumer)173     private void onRemoveConsumer(Consumer<IDreamOverlayClient> consumer) {
174         mConsumers.remove(consumer);
175     }
176 
177     private final class OverlayConnectionCallback implements
178             ObservableServiceConnection.Callback<IDreamOverlay> {
179 
180         private final IDreamOverlayClientCallback mClientCallback =
181                 new IDreamOverlayClientCallback.Stub() {
182                     @Override
183                     public void onDreamOverlayClient(IDreamOverlayClient client) {
184                         final Message msg =
185                                 mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client);
186                         mHandler.sendMessage(msg);
187                     }
188                 };
189 
190         @Override
onConnected( ObservableServiceConnection<IDreamOverlay> connection, IDreamOverlay service)191         public void onConnected(
192                 ObservableServiceConnection<IDreamOverlay> connection,
193                 IDreamOverlay service) {
194             try {
195                 service.getClient(mClientCallback);
196             } catch (RemoteException e) {
197                 Log.e(TAG, "could not get DreamOverlayClient", e);
198             }
199         }
200 
201         @Override
onDisconnected(ObservableServiceConnection<IDreamOverlay> connection, int reason)202         public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
203                 int reason) {
204             mClient = null;
205             // Cancel any pending messages about the overlay being ready, since it is no
206             // longer ready.
207             mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY);
208         }
209     }
210 
211     /**
212      * Injector for testing
213      */
214     @VisibleForTesting
215     public static class Injector {
216         /**
217          * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
218          * in tests with a fake clock.
219          */
buildConnection( Context context, Handler handler, Intent serviceIntent, int minConnectionDurationMs, int maxReconnectAttempts, int baseReconnectDelayMs)220         public PersistentServiceConnection<IDreamOverlay> buildConnection(
221                 Context context,
222                 Handler handler,
223                 Intent serviceIntent,
224                 int minConnectionDurationMs,
225                 int maxReconnectAttempts,
226                 int baseReconnectDelayMs) {
227             final Executor executor = handler::post;
228             final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
229             return new PersistentServiceConnection<>(
230                     context,
231                     executor,
232                     handler,
233                     IDreamOverlay.Stub::asInterface,
234                     serviceIntent,
235                     flags,
236                     minConnectionDurationMs,
237                     maxReconnectAttempts,
238                     baseReconnectDelayMs
239             );
240         }
241     }
242 }
243