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