I have made some additional researches, and managed to find a satisfying solution.
Here it comes:
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.
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().
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.
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.
I apologize for my bad English.
Code of working ConfictAvoidance solution:
IntentService class, that supports binding, so it is also AIDL backed service mentioned above. There is also BroadcastReceiver, which starts conflict checks.
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<String> packages = new ArrayList<String>(20);
final PackageManager man = getPackageManager();
//#ifdef DEBUG
Log.v(TAG, "Querying receivers: com.mylib.android.sdk.ACTION_DETECT_LIB");
//#endif
final List<ResolveInfo> 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) && 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<IRemoteSDK> mSdk = new AtomicReference<IRemoteSDK>();
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 > version)
{
return true;
}
else if (VERSION < version)
{
return false;
}
else
{
if (active)
{
return false;
}
else
{
return mUuid.compareTo(uuid) == 1;
}
}
}
catch (Exception e)
{
return false;
}
}
else
{
return false;
}
}
}
}
AIDL file:
package com.mylib.android.sdk;
interface IRemoteSDK
{
boolean isActive();
long[] getUUID();
int getSdkVersion();
}
Sample manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mylib.android.sdk"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="4"
android:targetSdkVersion="4" />
<service
android:name="com.mylib.android.sdk.utils.ConflictAvoidance"
android:exported="true" />
<receiver android:name="com.mylib.android.sdk.utils.ConflictAvoidance$DetectionReceiver" >
<intent-filter>
<action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
</application>
</manifest>
Action:
<action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" />
It is the common action, which is used to detect other apps with the library.
Log usage may look weird, but I use custom wrapper, which supports formatting, to decrease StringBuffers overhead when debugging.