1 /* 2 * Copyright (C) 2021 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 com.android.systemui.util.service; 18 19 import android.annotation.IntDef; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.IBinder; 25 import android.util.Log; 26 27 import com.android.systemui.dagger.qualifiers.Main; 28 import com.android.systemui.settings.UserTracker; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.lang.ref.WeakReference; 33 import java.util.ArrayList; 34 import java.util.Iterator; 35 import java.util.Optional; 36 import java.util.concurrent.Executor; 37 import java.util.function.Consumer; 38 39 import javax.inject.Inject; 40 41 /** 42 * {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection} 43 * that enables monitoring the status of a binder connection. It also aides in automatically 44 * converting a proxy into an internal wrapper type. 45 * @param <T> The type of the wrapper over the resulting service. 46 */ 47 public class ObservableServiceConnection<T> implements ServiceConnection { 48 private static final String TAG = "ObservableSvcConn"; 49 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 50 /** 51 * An interface for converting the service proxy into a given internal wrapper type. 52 * @param <T> The type of the wrapper over the resulting service. 53 */ 54 public interface ServiceTransformer<T> { 55 /** 56 * Called to convert the service proxy to the wrapper type. 57 * @param service The service proxy to create the wrapper type from. 58 * @return The wrapper type. 59 */ convert(IBinder service)60 T convert(IBinder service); 61 } 62 63 /** 64 * An interface for listening to the connection status. 65 * @param <T> The wrapper type. 66 */ 67 public interface Callback<T> { 68 /** 69 * Invoked when the service has been successfully connected to. 70 * @param connection The {@link ObservableServiceConnection} instance that is now connected 71 * @param proxy The service proxy converted into the typed wrapper. 72 */ onConnected(ObservableServiceConnection<T> connection, T proxy)73 void onConnected(ObservableServiceConnection<T> connection, T proxy); 74 75 /** 76 * Invoked when the service has been disconnected. 77 * @param connection The {@link ObservableServiceConnection} that is now disconnected. 78 * @param reason The reason for the disconnection. 79 */ onDisconnected(ObservableServiceConnection<T> connection, @DisconnectReason int reason)80 void onDisconnected(ObservableServiceConnection<T> connection, 81 @DisconnectReason int reason); 82 } 83 84 /** 85 * Disconnection was due to the resulting binding being {@code null}. 86 */ 87 public static final int DISCONNECT_REASON_NULL_BINDING = 1; 88 /** 89 * Disconnection was due to the remote end disconnecting. 90 */ 91 public static final int DISCONNECT_REASON_DISCONNECTED = 2; 92 /** 93 * Disconnection due to the binder dying. 94 */ 95 public static final int DISCONNECT_REASON_BINDING_DIED = 3; 96 /** 97 * Disconnection from an explicit unbinding. 98 */ 99 public static final int DISCONNECT_REASON_UNBIND = 4; 100 101 @Retention(RetentionPolicy.SOURCE) 102 @IntDef({ 103 DISCONNECT_REASON_NULL_BINDING, 104 DISCONNECT_REASON_DISCONNECTED, 105 DISCONNECT_REASON_BINDING_DIED, 106 DISCONNECT_REASON_UNBIND 107 }) 108 public @interface DisconnectReason {} 109 110 private final Context mContext; 111 private final Intent mServiceIntent; 112 private final UserTracker mUserTracker; 113 private final int mFlags; 114 private final Executor mExecutor; 115 private final ServiceTransformer<T> mTransformer; 116 private final ArrayList<WeakReference<Callback<T>>> mCallbacks; 117 private Optional<Integer> mLastDisconnectReason; 118 private T mProxy; 119 120 private boolean mBoundCalled; 121 122 /** 123 * Default constructor for {@link ObservableServiceConnection}. 124 * @param context The context from which the service will be bound with. 125 * @param serviceIntent The intent to bind service with. 126 * @param executor The executor for connection callbacks to be delivered on 127 * @param transformer A {@link ServiceTransformer} for transforming the resulting service 128 * into a desired type. 129 */ 130 @Inject ObservableServiceConnection(Context context, Intent serviceIntent, UserTracker userTracker, @Main Executor executor, ServiceTransformer<T> transformer)131 public ObservableServiceConnection(Context context, Intent serviceIntent, 132 UserTracker userTracker, 133 @Main Executor executor, 134 ServiceTransformer<T> transformer) { 135 mContext = context; 136 mServiceIntent = serviceIntent; 137 mUserTracker = userTracker; 138 mFlags = Context.BIND_AUTO_CREATE; 139 mExecutor = executor; 140 mTransformer = transformer; 141 mCallbacks = new ArrayList<>(); 142 mLastDisconnectReason = Optional.empty(); 143 } 144 145 /** 146 * Initiate binding to the service. 147 * @return {@code true} if initiating binding succeed, {@code false} otherwise. 148 */ bind()149 public boolean bind() { 150 boolean bindResult = false; 151 try { 152 bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags, 153 mUserTracker.getUserHandle()); 154 } catch (SecurityException e) { 155 Log.d(TAG, "Could not bind to service", e); 156 mContext.unbindService(this); 157 } 158 mBoundCalled = true; 159 if (DEBUG) { 160 Log.d(TAG, "bind. bound:" + bindResult); 161 } 162 return bindResult; 163 } 164 165 /** 166 * Disconnect from the service if bound. 167 */ unbind()168 public void unbind() { 169 onDisconnected(DISCONNECT_REASON_UNBIND); 170 } 171 172 /** 173 * Adds a callback for receiving connection updates. 174 * @param callback The {@link Callback} to receive future updates. 175 */ addCallback(Callback<T> callback)176 public void addCallback(Callback<T> callback) { 177 if (DEBUG) { 178 Log.d(TAG, "addCallback:" + callback); 179 } 180 181 mExecutor.execute(() -> { 182 final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator(); 183 184 while (iterator.hasNext()) { 185 if (iterator.next().get() == callback) { 186 return; 187 } 188 } 189 190 mCallbacks.add(new WeakReference<>(callback)); 191 192 // If not connected anymore, immediately inform new callback of disconnection and 193 // remove. 194 if (mProxy != null) { 195 callback.onConnected(this, mProxy); 196 } else if (mLastDisconnectReason.isPresent()) { 197 callback.onDisconnected(this, mLastDisconnectReason.get()); 198 } 199 }); 200 } 201 202 /** 203 * Removes previously added callback from receiving future connection updates. 204 * @param callback The {@link Callback} to be removed. 205 */ removeCallback(Callback callback)206 public void removeCallback(Callback callback) { 207 if (DEBUG) { 208 Log.d(TAG, "removeCallback:" + callback); 209 } 210 211 mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback)); 212 } 213 onDisconnected(@isconnectReason int reason)214 private void onDisconnected(@DisconnectReason int reason) { 215 if (DEBUG) { 216 Log.d(TAG, "onDisconnected:" + reason); 217 } 218 219 // If not bound or already unbound, do not proceed setting reason, unbinding, and 220 // notifying 221 if (!mBoundCalled) { 222 return; 223 } 224 225 mBoundCalled = false; 226 mLastDisconnectReason = Optional.of(reason); 227 mContext.unbindService(this); 228 mProxy = null; 229 230 applyToCallbacksLocked(callback-> callback.onDisconnected(this, 231 mLastDisconnectReason.get())); 232 } 233 234 @Override onServiceConnected(ComponentName name, IBinder service)235 public void onServiceConnected(ComponentName name, IBinder service) { 236 mExecutor.execute(() -> { 237 if (DEBUG) { 238 Log.d(TAG, "onServiceConnected"); 239 } 240 mProxy = mTransformer.convert(service); 241 applyToCallbacksLocked(callback -> callback.onConnected(this, mProxy)); 242 }); 243 } 244 applyToCallbacksLocked(Consumer<Callback<T>> applicator)245 private void applyToCallbacksLocked(Consumer<Callback<T>> applicator) { 246 final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator(); 247 248 while (iterator.hasNext()) { 249 final Callback cb = iterator.next().get(); 250 if (cb != null) { 251 applicator.accept(cb); 252 } else { 253 iterator.remove(); 254 } 255 } 256 } 257 258 @Override onServiceDisconnected(ComponentName name)259 public void onServiceDisconnected(ComponentName name) { 260 mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED)); 261 } 262 263 @Override onBindingDied(ComponentName name)264 public void onBindingDied(ComponentName name) { 265 mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED)); 266 } 267 268 @Override onNullBinding(ComponentName name)269 public void onNullBinding(ComponentName name) { 270 mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING)); 271 } 272 } 273