From 5d73b6d7a9e07d876ce35484c1b82f96407cf77a Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Fri, 6 Oct 2017 16:15:34 -0700 Subject: [PATCH] Introduce ServiceConnection#onNullBinding() Historically, if a service returns null from onBind(), the binding app gets no information about the outcome: the ServiceConnection is never invoked. We now introduce a new connection callback, onNullBinding(), for apps that need to detect this situation. When the service rejects the binding by returning null, the onNullBinding() callback in the associated ServiceConnection is invoked instead of onServiceConnected(). onNullBinding() has an empty default implementation, so there is no binary-compatibility impact of this new interface method. Bug: 67377345 Test: atest android.app.cts.ServiceTest Change-Id: I224512c118f7d6e5c1c2bb69eca1902882e73594 --- api/current.txt | 1 + core/java/android/app/LoadedApk.java | 5 ++++- core/java/android/content/Context.java | 15 +++++++++++---- .../java/android/content/ServiceConnection.java | 17 +++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/api/current.txt b/api/current.txt index 3acd26d2fc64e..2ee857b35227b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10072,6 +10072,7 @@ package android.content { public abstract interface ServiceConnection { method public default void onBindingDied(android.content.ComponentName); + method public default void onNullBinding(android.content.ComponentName); method public abstract void onServiceConnected(android.content.ComponentName, android.os.IBinder); method public abstract void onServiceDisconnected(android.content.ComponentName); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index de6230cf825aa..ebd101494588e 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1646,9 +1646,12 @@ public final class LoadedApk { if (dead) { mConnection.onBindingDied(name); } - // If there is a new service, it is now connected. + // If there is a new viable service, it is now connected. if (service != null) { mConnection.onServiceConnected(name, service); + } else { + // The binding machinery worked, but the remote returned null from onBind(). + mConnection.onNullBinding(name); } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 71998eeb14c7a..7c17fbf7a03b6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2801,10 +2801,17 @@ public abstract class Context { * example, if this Context is an Activity that is stopped, the service will * not be required to continue running until the Activity is resumed. * - *

This function will throw {@link SecurityException} if you do not + *

If the service does not support binding, it may return {@code null} from + * its {@link android.app.Service#onBind(Intent) onBind()} method. If it does, then + * the ServiceConnection's + * {@link ServiceConnection#onNullBinding(ComponentName) onNullBinding()} method + * will be invoked instead of + * {@link ServiceConnection#onServiceConnected(ComponentName, IBinder) onServiceConnected()}. + * + *

This method will throw {@link SecurityException} if the calling app does not * have permission to bind to the given service. * - *

Note: this method can not be called from a + *

Note: this method cannot be called from a * {@link BroadcastReceiver} component. A pattern you can use to * communicate from a BroadcastReceiver to a Service is to call * {@link #startService} with the arguments containing the command to be @@ -2827,8 +2834,8 @@ public abstract class Context { * {@link #BIND_WAIVE_PRIORITY}. * @return If you have successfully bound to the service, {@code true} is returned; * {@code false} is returned if the connection is not made so you will not - * receive the service object. However, you should still call - * {@link #unbindService} to release the connection. + * receive the service object. You should still call {@link #unbindService} + * to release the connection even if this method returned {@code false}. * * @throws SecurityException If the caller does not have permission to access the service * or the service can not be found. diff --git a/core/java/android/content/ServiceConnection.java b/core/java/android/content/ServiceConnection.java index 6ff4900212ffc..c16dbbe33aab9 100644 --- a/core/java/android/content/ServiceConnection.java +++ b/core/java/android/content/ServiceConnection.java @@ -63,4 +63,21 @@ public interface ServiceConnection { */ default void onBindingDied(ComponentName name) { } + + /** + * Called when the service being bound has returned {@code null} from its + * {@link android.app.Service#onBind(Intent) onBind()} method. This indicates + * that the attempting service binding represented by this ServiceConnection + * will never become usable. + * + *

The app which requested the binding must still call + * {@link Context#unbindService(ServiceConnection)} to release the tracking + * resources associated with this ServiceConnection even if this callback was + * invoked following {@link Context#bindService Context.bindService() bindService()}. + * + * @param name The concrete component name of the service whose binding + * has been rejected by the Service implementation. + */ + default void onNullBinding(ComponentName name) { + } }