Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I have made some additional researches, and managed to find a satisfying solution. Here it comes:</p> <p>A library has to be developed in a way, that each application which integrates it - publishes broadcast receiver with known action, eg. com.mylib.ACTION_DETECT.</p> <p>The library has to have additional Service, that publishes some AIDL interface, which helps with making decision - if current instance of library can be made active. The AIDL can have some useful methods for example getVersion(), isActive(), getUUID().</p> <p>The pattern for making decision is: if current instance has higher version number, that other one - it will become active. If current instance has lower version - it will deactivate itself, or stay deactivated if it is already deactivated. If current instance has equal version to other instance, then if other instance is not active, and other library's uuid is lower (through compareTo method) - it will activate itself. In other condition - it will deactivate itself. This cross checking ensures, that each library will make decision on its own - there will be no ambiguous cases, because each library will fetch required data from published AIDL backed Service of other libary instances in other apps.</p> <p>Next step is to prepare an IntentService, that is started each time new package is removed or added, or the application with library is started first time. The IntentService queries all packages for broadcast receivers, which implement com.mylib.ACTION_DETECT. Then it iterates through detected packages (rejecting it's own package), and binds to AIDL backed service of each other instance (the class name of AIDL service will be always the same, only application package would be different). After completing binding - we have clear situation - if applied pattern results "positive" (our instance has better version or higher uuid, or has been active already) then it implies, that other instances figured out themselves as "negative", and deactivated themselves. Of course the pattern has to be applied on each bound AIDL service.</p> <p>I apologize for my bad English.</p> <p><strong>Code of working ConfictAvoidance solution:</strong> IntentService class, that supports binding, so it is also AIDL backed service mentioned above. There is also BroadcastReceiver, which starts conflict checks.</p> <pre><code>public class ConflictAvoidance extends IntentService { private static final String TAG = ConflictAvoidance.class.getSimpleName(); private static final String PREFERENCES = "mylib_sdk_prefs"; private static final int VERSION = 1; private static final String KEY_BOOLEAN_PRIME_CHECK_DONE = "key_bool_prime_check_done"; private static final String KEY_BOOLEAN_ACTIVE = "key_bool_active"; private static final String KEY_LONG_MUUID = "key_long_muuid"; private static final String KEY_LONG_LUUID = "key_long_luuid"; private WakeLock mWakeLock; private SharedPreferences mPrefs; public ConflictAvoidance() { super(TAG); } private final IRemoteSDK.Stub mBinder = new IRemoteSDK.Stub() { @Override public boolean isActive() throws RemoteException { return mPrefs.getBoolean(KEY_BOOLEAN_ACTIVE, false); } @Override public long[] getUUID() throws RemoteException { return getLongUUID(); } @Override public int getSdkVersion() throws RemoteException { return 1; } }; @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { //#ifdef DEBUG Log.i(TAG, "onCreate()"); //#endif mWakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.acquire(); mPrefs = getSharedPreferences(PREFERENCES, MODE_PRIVATE); super.onCreate(); } @Override public void onDestroy() { //#ifdef DEBUG Log.i(TAG, "onDestroy()"); //#endif mWakeLock.release(); super.onDestroy(); } @Override protected void onHandleIntent(Intent arg) { //#ifdef DEBUG Log.d(TAG, "Conflict check"); //#endif final String packageName = getPackageName(); //#ifdef DEBUG Log.v(TAG, "Current package name: %s", packageName); //#endif final ArrayList&lt;String&gt; packages = new ArrayList&lt;String&gt;(20); final PackageManager man = getPackageManager(); //#ifdef DEBUG Log.v(TAG, "Querying receivers: com.mylib.android.sdk.ACTION_DETECT_LIB"); //#endif final List&lt;ResolveInfo&gt; receivers = man.queryBroadcastReceivers(new Intent("com.mylib.android.sdk.ACTION_DETECT_LIB"), 0); for (ResolveInfo receiver : receivers) { if (receiver.activityInfo != null) { final String otherPackageName = receiver.activityInfo.packageName; //#ifdef DEBUG Log.v(TAG, "Checking package: %s", otherPackageName); //#endif if (!packageName.equals(otherPackageName)) { packages.add(otherPackageName); } } } if (packages.isEmpty()) { //#ifdef DEBUG Log.i(TAG, "No other libraries found"); //#endif setup(true); } else { //#ifdef DEBUG Log.v(TAG, "Querying other packages"); //#endif final UUID uuid = getUUID(); for (String pkg : packages) { final Intent intent = new Intent(); intent.setClassName(pkg, "com.mylib.android.sdk.utils.ConflictAvoidance"); final RemoteConnection conn = new RemoteConnection(uuid); try { if (bindService(intent, conn, BIND_AUTO_CREATE)) { if (!conn.canActivateItself()) { setup(false); return; } } } finally { unbindService(conn); } } setup(true); } } private UUID getUUID() { final long[] uuid = getLongUUID(); return new UUID(uuid[0], uuid[1]); } private synchronized long[] getLongUUID() { if (mPrefs.contains(KEY_LONG_LUUID) &amp;&amp; mPrefs.contains(KEY_LONG_MUUID)) { return new long[] { mPrefs.getLong(KEY_LONG_MUUID, 0), mPrefs.getLong(KEY_LONG_LUUID, 0) }; } else { final long[] uuid = new long[2]; final UUID ruuid = UUID.randomUUID(); uuid[0] = ruuid.getMostSignificantBits(); uuid[1] = ruuid.getLeastSignificantBits(); mPrefs.edit().putLong(KEY_LONG_MUUID, uuid[0]).putLong(KEY_LONG_LUUID, uuid[1]).commit(); return uuid; } } private void setup(boolean active) { //#ifdef DEBUG Log.v(TAG, "setup(active:%b)", active); //#endif mPrefs.edit().putBoolean(KEY_BOOLEAN_ACTIVE, active).putBoolean(KEY_BOOLEAN_PRIME_CHECK_DONE, true).commit(); } public static StatusInfo getStatusInfo(Context context) { final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, MODE_PRIVATE); return new StatusInfo(prefs.getBoolean(KEY_BOOLEAN_ACTIVE, false), prefs.getBoolean(KEY_BOOLEAN_PRIME_CHECK_DONE, false)); } public static class DetectionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { context.startService(new Intent(context, ConflictAvoidance.class)); } } public static class StatusInfo { public final boolean isActive; public final boolean primeCheckDone; public StatusInfo(boolean isActive, boolean primeCheckDone) { this.isActive = isActive; this.primeCheckDone = primeCheckDone; } } protected static class RemoteConnection implements ServiceConnection { private final ConditionVariable var = new ConditionVariable(false); private final UUID mUuid; private final AtomicReference&lt;IRemoteSDK&gt; mSdk = new AtomicReference&lt;IRemoteSDK&gt;(); public RemoteConnection(UUID uuid) { super(); this.mUuid = uuid; } @Override public void onServiceConnected(ComponentName name, IBinder service) { //#ifdef DEBUG Log.v(TAG, "RemoteConnection.onServiceConnected(%s)", name.getPackageName()); //#endif mSdk.set(IRemoteSDK.Stub.asInterface(service)); var.open(); } @Override public void onServiceDisconnected(ComponentName name) { //#ifdef DEBUG Log.w(TAG, "RemoteConnection.onServiceDisconnected(%s)", name); //#endif var.open(); } public boolean canActivateItself() { //#ifdef DEBUG Log.v(TAG, "RemoteConnection.canActivateItself()"); //#endif var.block(30000); final IRemoteSDK sdk = mSdk.get(); if (sdk != null) { try { final int version = sdk.getSdkVersion(); final boolean active = sdk.isActive(); final UUID uuid; { final long[] luuid = sdk.getUUID(); uuid = new UUID(luuid[0], luuid[1]); } //#ifdef DEBUG Log.v(TAG, "Other library: ver: %d, active: %b, uuid: %s", version, active, uuid); //#endif if (VERSION &gt; version) { return true; } else if (VERSION &lt; version) { return false; } else { if (active) { return false; } else { return mUuid.compareTo(uuid) == 1; } } } catch (Exception e) { return false; } } else { return false; } } } } </code></pre> <p><strong>AIDL file:</strong></p> <pre><code>package com.mylib.android.sdk; interface IRemoteSDK { boolean isActive(); long[] getUUID(); int getSdkVersion(); } </code></pre> <p><strong>Sample manifest:</strong></p> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mylib.android.sdk" android:versionCode="1" android:versionName="1.0" &gt; &lt;uses-sdk android:minSdkVersion="4" android:targetSdkVersion="4" /&gt; &lt;service android:name="com.mylib.android.sdk.utils.ConflictAvoidance" android:exported="true" /&gt; &lt;receiver android:name="com.mylib.android.sdk.utils.ConflictAvoidance$DetectionReceiver" &gt; &lt;intent-filter&gt; &lt;action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" /&gt; &lt;/intent-filter&gt; &lt;intent-filter&gt; &lt;action android:name="android.intent.action.PACKAGE_ADDED" /&gt; &lt;action android:name="android.intent.action.PACKAGE_REMOVED" /&gt; &lt;action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /&gt; &lt;action android:name="android.intent.action.PACKAGE_REPLACED" /&gt; &lt;data android:scheme="package" /&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; &lt;/application&gt; &lt;/manifest&gt; </code></pre> <p>Action:</p> <pre><code>&lt;action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" /&gt; </code></pre> <p>It is the common action, which is used to detect other apps with the library.</p> <p>Log usage may look weird, but I use custom wrapper, which supports formatting, to decrease StringBuffers overhead when debugging.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload