Created
May 26, 2016 13:46
-
-
Save oasisfeng/71d09369fddaa742fabf2e4d3832abcf to your computer and use it in GitHub Desktop.
Service framework for friendly and efficient AIDL service binding in Android app.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.oasisfeng.android.service; | |
import android.annotation.SuppressLint; | |
import android.app.Service; | |
import android.content.Intent; | |
import android.os.Binder; | |
import android.os.IBinder; | |
import android.os.IInterface; | |
import android.support.annotation.Nullable; | |
import android.util.Log; | |
import com.oasisfeng.nevo.shared.BuildConfig; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Modifier; | |
import java.lang.reflect.ParameterizedType; | |
import java.lang.reflect.Type; | |
/** | |
z * A base class for AIDL service to simplify the boilerplate code, supporting both local and remote clients simultaneously. | |
* | |
* <p>Remember to declare your service in AndroidManifest.xml as: | |
* <b>(be sure to set android:exported="false" if you have no intention to expose this service)</b> | |
* <pre> | |
* <service android:name=".DemoService" android:exported="false"> | |
* <intent-filter> | |
* <action android:name="com.taobao.android.service.IDemoService" /> | |
* </intent-filter> | |
* </service> | |
* </pre> | |
* | |
* <pre> | |
* public class DemoService extends AidlService<IDemoService.Stub> { | |
* | |
* public class Impl extends IDemoService.Stub implements Closeable { | |
* | |
* protected String mName; | |
* | |
* @Override public String helloWorld(final String name) { | |
* mName = name; | |
* return "Hello " + name; | |
* } | |
* | |
* public void close() { | |
* // Optional destruction code here, as of onDestroy() in service. | |
* // No need to implement Closeable (or AutoCloseable) if nothing to do in destruction. | |
* } | |
* } | |
* | |
* protected IDemoService.Stub createBinder() { | |
* return new Impl(); | |
* } | |
* } | |
* </pre> | |
* | |
* @author Oasis | |
*/ | |
public abstract class AidlService<Stub extends Binder & IInterface> extends Service { | |
public AidlService() { | |
super.onCreate(); | |
final Type[] types = getActualTypeArguments(getClass()); | |
final Type type = types[0]; | |
if (! (type instanceof Class) || ! Binder.class.isAssignableFrom((Class) type)) | |
throw new IllegalArgumentException(type + " is not an AIDL stub"); | |
//noinspection unchecked | |
mInterface = (Class<? extends IInterface>) ((Class) type).getInterfaces()[0]; | |
if (BuildConfig.DEBUG) | |
for (Class<?> clazz = getClass(); clazz != AidlService.class; clazz = clazz.getSuperclass()) | |
for (final Field field : clazz.getDeclaredFields()) | |
if (! Modifier.isStatic(field.getModifiers())) | |
throw new IllegalStateException("AidlService-derived class must be stateless. " + | |
"Unlike Android service, it is not singleton. " + | |
"Consider moving fields to the binder class."); | |
} | |
/** | |
* Put initialization code in {@link #createBinder()} instead of this method. | |
* Only the binder, not the service instance is singleton. | |
*/ | |
@Override public final void onCreate() { super.onCreate(); } | |
@Override public final IBinder onBind(final Intent intent) { | |
Log.d(toString(), "onBind"); | |
mBinder = LocalAidlServices.bind(this, mInterface, intent); | |
return mBinder != null ? mBinder.asBinder() : null; | |
} | |
@Nullable Stub onBindFromLocal() { | |
final Stub stub = createBinder(); | |
mBinder = stub; | |
return stub; | |
} | |
/** | |
* Create the binder instance. This method will be called only once | |
* until the returned stub is no longer used (and thus closed). | |
*/ | |
protected abstract @Nullable Stub createBinder(); | |
void closeBinder() { | |
if (mBinder == null) throw new IllegalStateException("binder is null"); | |
synchronized (this) { | |
if (mBinder instanceof AutoCloseable) close((AutoCloseable) mBinder); | |
mBinder = null; | |
} | |
} | |
@SuppressLint("NewApi") // AutoCloseable is hidden but accessible | |
private void close(final AutoCloseable closeable) { | |
try { | |
Log.d(toString(), "close"); | |
closeable.close(); | |
} catch (final Exception e) { | |
Log.w(toString(), "Error closing " + closeable, e); | |
} | |
} | |
/** Called by AMS after all remote clients disconnect, while local bindings could be still in use. */ | |
@Override public final boolean onUnbind(final Intent intent) { | |
if (mBinder != null) LocalAidlServices.unbind(this, mBinder); | |
return false; | |
} | |
/** | |
* Put destruction code in {@link AutoCloseable#close()} of the stub instead of this method. | |
* Only the binder, not the service instance is singleton. | |
*/ | |
@Override public final void onDestroy() { super.onDestroy(); } | |
private static Type[] getActualTypeArguments(Class<?> derivation) { | |
while (derivation != null) { | |
final Type type = derivation.getGenericSuperclass(); | |
if (type instanceof ParameterizedType) { | |
final ParameterizedType ptype = (ParameterizedType) type; | |
if (AidlService.class.equals(ptype.getRawType())) | |
return ptype.getActualTypeArguments(); | |
} | |
derivation = derivation.getSuperclass(); | |
} | |
throw new IllegalArgumentException(); | |
} | |
@Override public final int onStartCommand(final Intent intent, final int flags, final int startId) { | |
Log.w(toString(), "Start operation is not allowed for AIDL service."); | |
stopSelf(startId); | |
return START_NOT_STICKY; | |
} | |
@Override public String toString() { | |
if (mName != null) return mName; | |
final String name = getSimpleName(); | |
return mName = name.substring(name.indexOf('$') + 1); | |
} | |
private String getSimpleName() { | |
final String name = getClass().getName(); | |
if (name.endsWith("$Service")) return name.substring(name.lastIndexOf('.') + 1, name.length() - 8/* "$Service".length */); | |
return name.substring(name.lastIndexOf('.') + 1); | |
} | |
private final Class<? extends IInterface> mInterface; | |
private String mName; | |
private IInterface mBinder; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.oasisfeng.android.service; | |
import android.annotation.TargetApi; | |
import android.app.Activity; | |
import android.app.Application; | |
import android.app.Service; | |
import android.content.BroadcastReceiver; | |
import android.content.ComponentCallbacks; | |
import android.content.ComponentName; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.ServiceConnection; | |
import android.os.Build.VERSION; | |
import android.os.Build.VERSION_CODES; | |
import android.os.Debug; | |
import android.os.IBinder; | |
import android.os.IInterface; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.util.Log; | |
import com.oasisfeng.android.util.MultiCatchROECompat; | |
import java.io.Closeable; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Proxy; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* Optimized implementation for local-only service. | |
* | |
* No more AMS IPC involved, no more asynchronous pains. | |
* | |
* @author Oasis | |
*/ | |
class LocalAidlServices { | |
/** | |
* Two incoming paths: | |
* Local-binding: bindWith() -> AidlService.onCreate(), onBind() -> bindWith() | |
* Sys-binding: ActivityThread.handleCreateService() -> AidlService.onCreate(), onBind() -> bindWith() | |
* | |
* @param intent must be explicit (with component pre-resolved) | |
*/ | |
static @Nullable <I extends IInterface> I bind(final Context context, @NonNull final Class<I> service_interface, final Intent intent) { | |
if (intent.getComponent() == null) | |
throw new IllegalArgumentException("Intent must be explicit (with component set)"); | |
//noinspection SynchronizationOnLocalVariableOrMethodParameter, | |
synchronized (service_interface) { // Use interface class as a sparse lock to avoid locking sServices during service initialization. | |
return bindLocked(context, service_interface, intent); | |
} | |
} | |
private static @Nullable <I extends IInterface> I bindLocked(final Context context, @NonNull final Class<I> service_interface, final Intent intent) { | |
final IBinder binder; | |
ServiceRecord record = sServices.get(service_interface); | |
if (record == null) { | |
final IBinder sys_binder = sDummyReceiver.peekService(context, intent); | |
if (sys_binder != null) { // The service is already bound by system, initiate a new binding via AMS. | |
record = new ServiceRecord(service_interface, null, sys_binder); | |
if (! context.bindService(intent, record, Context.BIND_AUTO_CREATE)) { | |
Log.e(TAG, "Failed to bind service with " + intent); | |
return null; | |
} | |
} else { // Create the service instance locally | |
final Service service = createService(context, intent.getComponent().getClassName()); | |
if (service == null) return null; | |
binder = bindService(service, intent); | |
if (binder == null) { | |
destroyService(service); | |
return null; | |
} | |
record = new ServiceRecord(service_interface, service, binder); | |
} | |
sServices.put(service_interface, record); | |
} | |
// Wrap with dynamic proxy, which is later used to differentiate instances in unbind(). | |
final ProxyHandler handler = new ProxyHandler(service_interface, record.binder); | |
final Class[] interfaces = collectInterfacesToProxy(record.binder, service_interface); | |
@SuppressWarnings("unchecked") final I instance = (I) Proxy.newProxyInstance(context.getClassLoader(), interfaces, handler); | |
record.instances.add(instance); | |
return instance; | |
} | |
private static Class[] collectInterfacesToProxy(final Object impl, final Class<?> service_interface) { | |
final Class<?>[] direct_interfaces = impl.getClass().getInterfaces(); | |
if (direct_interfaces.length == 0) return new Class[] { service_interface }; | |
for (int i = 0; i < direct_interfaces.length; i ++) { | |
final Class<?> direct_interface = direct_interfaces[i]; | |
if (direct_interface == Closeable.class || direct_interface == AutoCloseable.class) { // Exclude | |
direct_interfaces[i] = service_interface; | |
return direct_interfaces; | |
} | |
} | |
final Class<?>[] merged = Arrays.copyOf(direct_interfaces, direct_interfaces.length + 1); | |
merged[direct_interfaces.length] = service_interface; | |
return merged; | |
} | |
static <I extends IInterface> boolean unbind(final Context context, final I instance) { | |
if (instance == null) throw new IllegalArgumentException("instance is null"); | |
final InvocationHandler invocation_handler; | |
final Class<? extends IInterface> proxy_class = instance.getClass(); | |
if (! Proxy.isProxyClass(proxy_class) | |
|| ! ((invocation_handler = Proxy.getInvocationHandler(instance)) instanceof ProxyHandler)) | |
throw new IllegalArgumentException("Not a service instance: " + instance); | |
final ProxyHandler handler = (ProxyHandler) invocation_handler; | |
handler.binder = null; // Invalidate the proxy | |
return unbind(context, handler.itf, instance); | |
} | |
private static <I extends IInterface> boolean unbind(final Context context, final Class<? extends IInterface> service_interface, final I instance) { | |
final ServiceRecord record = sServices.get(service_interface); | |
if (record == null) throw new IllegalArgumentException("No service bound for " + service_interface.getName()); | |
final Iterator iterator = record.instances.iterator(); | |
// List.remove() is not working on list of dynamic proxies, since equals() method is forwarded. | |
while (iterator.hasNext()) if (iterator.next() == instance) { | |
iterator.remove(); | |
if (record.instances.isEmpty()) { | |
sServices.remove(service_interface); | |
// TODO: Defer the unbinding and destroying | |
if (record.service == null) | |
try { | |
context.unbindService(record); | |
} catch (final RuntimeException e) { | |
Log.d(TAG, "Ignore failure in service unbinding: " + e); | |
} | |
else { | |
unbindService(record.service, makeIntent(record.service, service_interface)); | |
destroyService(record.service); | |
} | |
return true; | |
} else return false; | |
} | |
throw new IllegalArgumentException("Instance not found in service connections of " + service_interface + ": " | |
+ instance.getClass().getName() + "@" + System.identityHashCode(instance)); | |
} | |
private static class ProxyHandler implements InvocationHandler { | |
private ProxyHandler(final Class<? extends IInterface> service_interface, final IBinder binder) { | |
this.binder = binder; | |
this.itf = service_interface; | |
} | |
@Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { | |
if (binder == null) throw new IllegalStateException("Service is already unbound"); | |
try { | |
return method.invoke(binder, args); | |
} catch (final InvocationTargetException e) { | |
throw e.getTargetException(); | |
} | |
} | |
private IBinder binder; | |
private final Class<? extends IInterface> itf; | |
} | |
private static Intent makeIntent(final Context context, final Class<? extends IInterface> service_interface) { | |
return new Intent(service_interface.getName()).setPackage(context.getPackageName()); | |
} | |
private static void unbindService(final Service service, final Intent intent) { | |
if (service instanceof AidlService) | |
((AidlService) service).closeBinder(); | |
else try { | |
final boolean rebind = service.onUnbind(intent); // TODO: Support rebind if true is returned. | |
if (rebind) throw new UnsupportedOperationException("Sorry, onRebind() is not yet supported."); | |
} catch (final RuntimeException e) { | |
Log.e(TAG, "Error unbinding " + service, e); | |
} | |
} | |
static void destroyService(final Service service) { | |
unregisterComponentCallbacks(service.getApplication(), service); | |
try { | |
final long start = Debug.threadCpuTimeNanos(); | |
service.onDestroy(); | |
logExcessiveElapse(start, 5, service, ".onDestroy()"); | |
} catch (final RuntimeException e) { | |
Log.e(TAG, "Error destroying " + service, e); | |
} | |
} | |
static @Nullable Service createService(final Context context, final String classname) { | |
// Load class | |
final Class<?> clazz; | |
try { | |
clazz = context.getClassLoader().loadClass(classname); | |
} catch (final ClassNotFoundException e) { | |
Log.w(TAG, e.toString()); | |
return null; | |
} | |
if (! Service.class.isAssignableFrom(clazz)) { | |
Log.w(TAG, "Not service class: " + clazz); | |
return null; | |
} | |
@SuppressWarnings("unchecked") final Class<? extends Service> service_class = (Class<? extends Service>) clazz; | |
// Instantiate | |
final Service service; final String class_name; | |
try { | |
final long start = Debug.threadCpuTimeNanos(); | |
service = service_class.newInstance(); | |
logExcessiveElapse(start, 2, class_name = service_class.getName(), "()"); | |
} catch (final InstantiationException e) { | |
Log.e(TAG, "Failed to instantiate " + classname, e); | |
return null; | |
} catch (final IllegalAccessException e) { | |
Log.e(TAG, "Constructor of " + classname + " is inaccessible", e); | |
return null; | |
} catch (final RuntimeException e) { | |
Log.e(TAG, "Error instantiating " + classname, e); | |
return null; | |
} | |
// Attach | |
final Application application = getApplication(context); | |
attach(context, service_class, service, application); | |
// Create | |
try { | |
final long start = Debug.threadCpuTimeNanos(); | |
service.onCreate(); | |
logExcessiveElapse(start, 5, class_name, ".onCreate()"); | |
} catch (final RuntimeException e) { | |
Log.e(TAG, service + ".onCreate()", e); | |
} | |
// Hookup lifecycle callbacks | |
registerComponentCallbacks(service.getApplication(), service); | |
return service; | |
} | |
private static @Nullable IBinder bindService(final Service service, final Intent intent) { | |
// Bind | |
IBinder binder = null; | |
try { | |
final long start = Debug.threadCpuTimeNanos(); | |
binder = service instanceof AidlService ? ((AidlService) service).onBindFromLocal() : service.onBind(intent); | |
logExcessiveElapse(start, 2, service, ".onBind()"); | |
} catch (final RuntimeException e) { | |
Log.e(TAG, service + ".onBind()", e); | |
} | |
if (binder == null) { // Error running onBind() or null is returned by onBind(). | |
destroyService(service); | |
try { | |
final long start = Debug.threadCpuTimeNanos(); | |
service.onDestroy(); | |
logExcessiveElapse(start, 5, service, ".onDestroy()"); | |
} catch (final RuntimeException e) { | |
Log.e(TAG, service + ".onDestroy()", e); | |
} | |
} | |
return binder; | |
} | |
private static void logExcessiveElapse(final long start_thread_cpu_nanos, final long tolerable_duration_ms, final Object procedure, final String postfix) { | |
final long duration_ms = (Debug.threadCpuTimeNanos() - start_thread_cpu_nanos) / 1_000_000; | |
if (duration_ms <= tolerable_duration_ms) return; | |
Log.w(TAG, procedure.toString() + (postfix != null ? postfix : "") + " consumed " + duration_ms + "ms (thread CPU time)"); | |
} | |
@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) | |
private static void registerComponentCallbacks(final Application app, final ComponentCallbacks callbacks) { | |
if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) return; | |
app.registerComponentCallbacks(callbacks); | |
} | |
@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) | |
private static void unregisterComponentCallbacks(final Application app, final ComponentCallbacks callbacks) { | |
if (app == null || VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) return; | |
app.unregisterComponentCallbacks(callbacks); | |
} | |
private static void attach(final Context context, final Class<? extends Service> service_class, | |
final Service service, final Application application) { | |
if (Service_attach == null) return; | |
try { | |
Service_attach.invoke(service, context, null, service_class.getName(), null, application, null); | |
} catch (final IllegalAccessException e) { | |
Log.e(TAG, "Unexpected exception when attaching service.", e); | |
} catch (final InvocationTargetException e) { | |
throw new RuntimeException(e.getTargetException()); | |
} | |
} | |
private static Application getApplication(final Context context) { | |
if (context instanceof Activity) return ((Activity) context).getApplication(); | |
if (context instanceof Service) return ((Service) context).getApplication(); | |
final Context app_context = context.getApplicationContext(); | |
if (app_context instanceof Application) return (Application) app_context; | |
Log.w(TAG, "Cannot discover application from context " + context); | |
return null; | |
} | |
private static final Map<Class<? extends IInterface>, ServiceRecord> sServices = Collections.synchronizedMap(new HashMap<>()); | |
private static final BroadcastReceiver sDummyReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) {}}; | |
private static final String TAG = "LocalSvc"; | |
// Method signature: (useless parameters for AIDL service - thread, token, activityManager) | |
// public final void attach(Context context, ActivityThread thread, String className, IBinder token, Application application, Object activityManager) | |
private static final Method Service_attach; | |
static { | |
Method method = null; | |
try { | |
final Class<?> ActivityThread = Class.forName("android.app.ActivityThread"); | |
method = Service.class.getDeclaredMethod("attach", Context.class, ActivityThread, String.class, | |
IBinder.class, Application.class, Object.class); | |
method.setAccessible(true); | |
} catch (final ClassNotFoundException | NoSuchMethodException | MultiCatchROECompat e) { | |
Log.e(TAG, "Incompatible ROM", e); | |
} | |
Service_attach = method; | |
} | |
private static class ServiceRecord implements ServiceConnection { | |
final Class<? extends IInterface> itf; | |
final @Nullable Service service; | |
final IBinder binder; | |
final List<IInterface> instances = new ArrayList<>(); | |
@Override public void onServiceConnected(final ComponentName name, final IBinder binder) { | |
if (binder != this.binder) Log.e(TAG, "Inconsistent binder: " + binder + " != " + this.binder); | |
} | |
@Override public void onServiceDisconnected(final ComponentName name) { | |
// TODO | |
} | |
ServiceRecord(final Class<? extends IInterface> service_interface, final @Nullable Service service, final IBinder binder) { | |
itf = service_interface; | |
this.service = service; | |
this.binder = binder; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.oasisfeng.android.service; | |
import android.app.Service; | |
import android.content.BroadcastReceiver; | |
import android.content.ComponentName; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.ServiceConnection; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.PackageInfo; | |
import android.content.pm.PackageManager; | |
import android.content.pm.ResolveInfo; | |
import android.os.IBinder; | |
import android.os.IInterface; | |
import android.os.Process; | |
import android.support.annotation.CheckResult; | |
import android.support.annotation.Nullable; | |
import android.util.Log; | |
import java.util.Comparator; | |
import java.util.IdentityHashMap; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* Simplify the AIDL service usage | |
* | |
* Created by Oasis on 2015/5/19. | |
*/ | |
public class Services { | |
public interface ServiceReadyThrows<I extends IInterface, E extends Exception> { | |
void onServiceReady(I service) throws E; | |
} | |
public interface AidlStub<I extends IInterface> { | |
I asInterface(IBinder obj); | |
} | |
/** Bind to the specified service for one-time procedure, then unbind the service. */ | |
public static <I extends IInterface, E extends Exception> boolean use(final Context context, final Class<I> itf, final AidlStub<I> stub, final ServiceReadyThrows<I, E> procedure) { | |
return bind(context, itf, new ServiceConnection() { | |
@Override public void onServiceConnected(final ComponentName name, final IBinder binder) { | |
final I service = stub.asInterface(binder); | |
try { | |
procedure.onServiceReady(service); | |
} catch (final Exception e) { | |
Log.e(TAG, "Error in " + procedure, e); | |
} finally { | |
try { context.unbindService(this); } catch (final RuntimeException ignored) {} | |
} | |
} | |
@Override public void onServiceDisconnected(final ComponentName name) {} | |
}); | |
} | |
public static boolean bind(final Context context, final Class<? extends IInterface> service_interface, final ServiceConnection conn) { | |
final Intent service_intent = buildServiceIntent(context, service_interface); | |
if (service_intent == null) { | |
Log.w(TAG, "No matched service for " + service_interface.getName()); | |
return false; | |
} | |
return context.bindService(service_intent, conn, Context.BIND_AUTO_CREATE); | |
} | |
public static @CheckResult @Nullable <I extends IInterface> I bindLocal(final Context context, final Class<I> service_interface) { | |
if (! sRegistry.isEmpty()) { | |
@SuppressWarnings("unchecked") final I instance = (I) sRegistry.get(service_interface); | |
if (instance != null) return instance; | |
} | |
return LocalAidlServices.bind(context, service_interface, buildServiceIntent(context, service_interface)); | |
} | |
public static @Nullable Service createLocal(final Context context, final String classname) { | |
return LocalAidlServices.createService(context, classname); | |
} | |
public static void destroyLocal(final Service service) { | |
LocalAidlServices.destroyService(service); | |
} | |
public static <I extends IInterface> void unbindLocal(final Context context, final I instance) { | |
if (sRegistry.containsValue(instance)) return; // TODO: Reference counting registered services | |
LocalAidlServices.unbind(context, instance); | |
} | |
public static <I extends IInterface> void register(final Class<I> service_interface, final I instance) { | |
final IInterface existent = sRegistry.get(service_interface); | |
if (existent != null) { | |
if (existent == instance) return; | |
throw new IllegalStateException(service_interface + " was already registered with " + existent.getClass()); | |
} | |
sRegistry.put(service_interface, instance); | |
} | |
public static <I extends IInterface> boolean unregister(final Class<I> service_interface, final I instance) { | |
final IInterface existent = sRegistry.get(service_interface); | |
if (existent == null) return false; | |
if (instance != existent) throw new IllegalStateException(service_interface + " was registered with " + existent + ", but being unregistered with " + instance); | |
sRegistry.remove(service_interface); | |
return true; | |
} | |
public static <I extends IInterface> I peekService(final Context context, final Class<I> service_interface) { | |
final IBinder binder = peekService(context, buildServiceIntent(context, service_interface)); | |
if (binder == null) return null; | |
try { //noinspection unchecked | |
return (I) service_interface.getMethod("asInterface", IBinder.class).invoke(binder); | |
} catch (final Exception e) { | |
Log.e(TAG, "Error calling " + service_interface.getCanonicalName() + ".asInterface() on " + binder, e); | |
return null; | |
} | |
} | |
public static IBinder peekService(final Context context, final Intent intent) { | |
return sDummyReceiver.peekService(context, intent); | |
} | |
private static @Nullable Intent buildServiceIntent(final Context context, final Class<?> service_interface) { | |
final String name = service_interface.getName(); final Intent intent = new Intent(name); | |
final PackageManager pm = context.getPackageManager(); final int uid = android.os.Process.myUid(); | |
final ComponentName component = resolveServiceIntent(context, intent, (left, right) -> { | |
final ApplicationInfo app_left = left.serviceInfo.applicationInfo, app_right = right.serviceInfo.applicationInfo; | |
// 1 - UID | |
int rank_left = app_left.uid != uid ? 1 : 0; | |
int rank_right = app_right.uid != uid ? 1 : 0; | |
if (rank_left != rank_right) return rank_left - rank_right; | |
// 2 - Priority | |
final int priority_diff = left.priority - right.priority; | |
if (priority_diff != 0) return (- priority_diff); // Higher priority wins | |
// 3 - Package update time | |
if (! app_left.packageName.equals(app_right.packageName)) { | |
final PackageInfo pkg_left, pkg_right; | |
try { | |
pkg_left = pm.getPackageInfo(app_left.packageName, 0); | |
} catch (final PackageManager.NameNotFoundException e) { return 1; } // right wins | |
try { | |
pkg_right = pm.getPackageInfo(app_right.packageName, 0); | |
} catch (final PackageManager.NameNotFoundException e) { return - 1; } // left wins | |
if (pkg_left.lastUpdateTime != pkg_right.lastUpdateTime) | |
return (int) - (pkg_left.lastUpdateTime - pkg_right.lastUpdateTime); // Newer wins | |
} | |
// 4 - Package name | |
final String pkg = context.getPackageName(); | |
rank_left = pkg.equals(app_left.packageName) ? 0 : 1; | |
rank_right = pkg.equals(app_right.packageName) ? 0 : 1; | |
if (rank_left != rank_right) return rank_left - rank_right; | |
return 0; | |
}); | |
if (component == null) return null; | |
intent.setComponent(component); | |
return intent; | |
} | |
/** @param comparator comparator for resolving candidates, smaller wins */ | |
private static @Nullable ComponentName resolveServiceIntent(final Context context, final Intent intent, final Comparator<ResolveInfo> comparator) { | |
final List<ResolveInfo> matches = context.getPackageManager().queryIntentServices(intent, 0); | |
if (matches == null || matches.isEmpty()) return null; | |
ResolveInfo best_match = matches.get(0); | |
final int my_uid = Process.myUid(); | |
for (final ResolveInfo match : matches) { | |
if (! match.serviceInfo.exported && match.serviceInfo.applicationInfo.uid != my_uid) continue; | |
if (best_match != match && comparator.compare(match, best_match) < 0) | |
best_match = match; | |
} | |
final ComponentName component = new ComponentName(best_match.serviceInfo.packageName, best_match.serviceInfo.name); | |
if (matches.size() > 1) Log.d(TAG, "Final match for " + intent + " among " + matches.size() + ": " + component.flattenToShortString()); | |
return component; | |
} | |
private static final Map<Class, IInterface> sRegistry = new IdentityHashMap<>(); | |
private static final BroadcastReceiver sDummyReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) {}}; | |
private static final String TAG = "Services"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment