1 /* 2 * Copyright 2020 The gRPC Authors 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 io.grpc.binder.internal; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.os.IBinder; 24 import androidx.annotation.AnyThread; 25 import androidx.annotation.MainThread; 26 import com.google.common.annotations.VisibleForTesting; 27 import io.grpc.Status; 28 import java.util.concurrent.Executor; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 import javax.annotation.Nullable; 32 import javax.annotation.concurrent.GuardedBy; 33 import javax.annotation.concurrent.ThreadSafe; 34 35 /** 36 * Manages an Android binding that's restricted to at most one connection to the remote Service. 37 * 38 * <p>A note on synchronization & locking in this class. Clients of this class are likely to manage 39 * their own internal state via synchronization. In order to avoid deadlocks, we must not hold any 40 * locks while calling observer callbacks. 41 * 42 * <p>For this reason, while internal consistency is handled with synchronization (the state field), 43 * consistency on our observer callbacks is ensured by doing everything on the application's main 44 * thread. 45 */ 46 @ThreadSafe 47 final class ServiceBinding implements Bindable, ServiceConnection { 48 49 private static final Logger logger = Logger.getLogger(ServiceBinding.class.getName()); 50 51 // States can only ever transition in one direction. 52 private enum State { 53 NOT_BINDING, 54 BINDING, 55 BOUND, 56 UNBOUND, 57 } 58 59 private final Intent bindIntent; 60 private final int bindFlags; 61 private final Observer observer; 62 private final Executor mainThreadExecutor; 63 64 @GuardedBy("this") 65 private State state; 66 67 // The following fields are intentionally not guarded, since (aside from the constructor), 68 // they're only modified in the main thread. The constructor contains a synchronized block 69 // to ensure there's a write barrier when these fields are first written. 70 71 @Nullable private Context sourceContext; // Only null in the unbound state. 72 73 private State reportedState; // Only used on the main thread. 74 75 @AnyThread ServiceBinding( Executor mainThreadExecutor, Context sourceContext, Intent bindIntent, int bindFlags, Observer observer)76 ServiceBinding( 77 Executor mainThreadExecutor, 78 Context sourceContext, 79 Intent bindIntent, 80 int bindFlags, 81 Observer observer) { 82 // We need to synchronize here ensure other threads see all 83 // non-final fields initialized after the constructor. 84 synchronized (this) { 85 this.bindIntent = bindIntent; 86 this.bindFlags = bindFlags; 87 this.observer = observer; 88 this.sourceContext = sourceContext; 89 this.mainThreadExecutor = mainThreadExecutor; 90 state = State.NOT_BINDING; 91 reportedState = State.NOT_BINDING; 92 } 93 } 94 95 @MainThread notifyBound(IBinder binder)96 private void notifyBound(IBinder binder) { 97 if (reportedState == State.NOT_BINDING) { 98 reportedState = State.BOUND; 99 logger.log(Level.FINEST, "notify bound - notifying"); 100 observer.onBound(binder); 101 } 102 } 103 104 @MainThread notifyUnbound(Status reason)105 private void notifyUnbound(Status reason) { 106 logger.log(Level.FINEST, "notify unbound ", reason); 107 clearReferences(); 108 if (reportedState != State.UNBOUND) { 109 reportedState = State.UNBOUND; 110 logger.log(Level.FINEST, "notify unbound - notifying"); 111 observer.onUnbound(reason); 112 } 113 } 114 115 @AnyThread 116 @Override bind()117 public synchronized void bind() { 118 if (state == State.NOT_BINDING) { 119 state = State.BINDING; 120 Status bindResult = bindInternal(sourceContext, bindIntent, this, bindFlags); 121 if (!bindResult.isOk()) { 122 handleBindServiceFailure(sourceContext, this); 123 state = State.UNBOUND; 124 mainThreadExecutor.execute(() -> notifyUnbound(bindResult)); 125 } 126 } 127 } 128 bindInternal( Context context, Intent bindIntent, ServiceConnection conn, int flags)129 private static Status bindInternal( 130 Context context, Intent bindIntent, ServiceConnection conn, int flags) { 131 try { 132 if (!context.bindService(bindIntent, conn, flags)) { 133 return Status.UNIMPLEMENTED.withDescription( 134 "bindService(" + bindIntent + ") returned false"); 135 } 136 return Status.OK; 137 } catch (SecurityException e) { 138 return Status.PERMISSION_DENIED.withCause(e).withDescription( 139 "SecurityException from bindService"); 140 } catch (RuntimeException e) { 141 return Status.INTERNAL.withCause(e).withDescription( 142 "RuntimeException from bindService"); 143 } 144 } 145 146 // Over the years, the API contract for Context#bindService() has been inconsistent on the subject 147 // of error handling. But inspecting recent AOSP implementations shows that, internally, 148 // bindService() retains a reference to the ServiceConnection when it throws certain Exceptions 149 // and even when it returns false. To avoid leaks, we *always* call unbindService() in case of 150 // error and simply ignore any "Service not registered" IAE and other RuntimeExceptions. handleBindServiceFailure(Context context, ServiceConnection conn)151 private static void handleBindServiceFailure(Context context, ServiceConnection conn) { 152 try { 153 context.unbindService(conn); 154 } catch (RuntimeException e) { 155 logger.log(Level.FINE, "Could not clean up after bindService() failure.", e); 156 } 157 } 158 159 @Override 160 @AnyThread unbind()161 public void unbind() { 162 unbindInternal(Status.CANCELLED); 163 } 164 165 @AnyThread unbindInternal(Status reason)166 void unbindInternal(Status reason) { 167 Context unbindFrom = null; 168 synchronized (this) { 169 if (state == State.BINDING || state == State.BOUND) { 170 unbindFrom = sourceContext; 171 } 172 state = State.UNBOUND; 173 } 174 mainThreadExecutor.execute(() -> notifyUnbound(reason)); 175 if (unbindFrom != null) { 176 unbindFrom.unbindService(this); 177 } 178 } 179 180 @MainThread clearReferences()181 private void clearReferences() { 182 sourceContext = null; 183 } 184 185 @Override 186 @MainThread onServiceConnected(ComponentName className, IBinder binder)187 public void onServiceConnected(ComponentName className, IBinder binder) { 188 boolean bound = false; 189 synchronized (this) { 190 if (state == State.BINDING) { 191 state = State.BOUND; 192 bound = true; 193 } 194 } 195 if (bound) { 196 // We call notify directly because we know we're on the main thread already. 197 // (every millisecond counts in this path). 198 notifyBound(binder); 199 } 200 } 201 202 @Override 203 @MainThread onServiceDisconnected(ComponentName name)204 public void onServiceDisconnected(ComponentName name) { 205 unbindInternal(Status.UNAVAILABLE.withDescription("onServiceDisconnected: " + name)); 206 } 207 208 @Override 209 @MainThread onNullBinding(ComponentName name)210 public void onNullBinding(ComponentName name) { 211 unbindInternal(Status.UNIMPLEMENTED.withDescription("onNullBinding: " + name)); 212 } 213 214 @Override 215 @MainThread onBindingDied(ComponentName name)216 public void onBindingDied(ComponentName name) { 217 unbindInternal(Status.UNAVAILABLE.withDescription("onBindingDied: " + name)); 218 } 219 220 @VisibleForTesting isSourceContextCleared()221 synchronized boolean isSourceContextCleared() { 222 return sourceContext == null; 223 } 224 } 225