Merge "In permission review mode, always request user's consent to toggle BT."
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d4dcacc..0c4573b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -391,7 +391,7 @@
public ContextHubManager(Context context, Looper mainLooper) {
mMainLooper = mainLooper;
- IBinder b = ServiceManager.getService(ContextHubService.CONTEXTHUB_SERVICE);
+ IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE);
if (b != null) {
mContextHubService = IContextHubService.Stub.asInterface(b);
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 83866b3..e617e0a 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -39,6 +39,9 @@
public native final void transact(
int code, HwParcel request, HwParcel reply, int flags);
+ public native boolean linkToDeath(DeathRecipient recipient, long cookie);
+ public native boolean unlinkToDeath(DeathRecipient recipient);
+
private static native final long native_init();
private native final void native_setup_empty();
@@ -52,5 +55,9 @@
128 /* size */);
}
+ private static final void sendDeathNotice(DeathRecipient recipient, long cookie) {
+ recipient.serviceDied(cookie);
+ }
+
private long mNativeContext;
}
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index 76e881e..f93bfd7 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -26,4 +26,16 @@
int code, HwParcel request, HwParcel reply, int flags);
public IHwInterface queryLocalInterface(String descriptor);
+
+ /**
+ * Interface for receiving a callback when the process hosting a service
+ * has gone away.
+ */
+ public interface DeathRecipient {
+ public void serviceDied(long cookie);
+ }
+
+ public boolean linkToDeath(DeathRecipient recipient, long cookie);
+
+ public boolean unlinkToDeath(DeathRecipient recipient);
}
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 1d5d6d5..0a7d84d 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -25,6 +25,7 @@
#include <JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/Status.h>
+#include <ScopedUtfChars.h>
#include <nativehelper/ScopedLocalRef.h>
#include "core_jni_helpers.h"
@@ -38,26 +39,196 @@
namespace android {
static struct fields_t {
+ jclass proxy_class;
jfieldID contextID;
jmethodID constructID;
+ jmethodID sendDeathNotice;
+} gProxyOffsets;
-} gFields;
+static struct class_offsets_t
+{
+ jmethodID mGetName;
+} gClassOffsets;
+
+static JavaVM* jnienv_to_javavm(JNIEnv* env)
+{
+ JavaVM* vm;
+ return env->GetJavaVM(&vm) >= 0 ? vm : NULL;
+}
+
+static JNIEnv* javavm_to_jnienv(JavaVM* vm)
+{
+ JNIEnv* env;
+ return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
+}
+
+// ----------------------------------------------------------------------------
+class HwBinderDeathRecipient : public hardware::IBinder::DeathRecipient
+{
+public:
+ HwBinderDeathRecipient(JNIEnv* env, jobject object, jlong cookie, const sp<HwBinderDeathRecipientList>& list)
+ : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),
+ mObjectWeak(NULL), mCookie(cookie), mList(list)
+ {
+ // These objects manage their own lifetimes so are responsible for final bookkeeping.
+ // The list holds a strong reference to this object.
+ list->add(this);
+ }
+
+ void binderDied(const wp<hardware::IBinder>& who)
+ {
+ if (mObject != NULL) {
+ JNIEnv* env = javavm_to_jnienv(mVM);
+
+ env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, mObject, mCookie);
+ if (env->ExceptionCheck()) {
+ ALOGE("Uncaught exception returned from death notification.");
+ env->ExceptionClear();
+ }
+
+ // Serialize with our containing HwBinderDeathRecipientList so that we can't
+ // delete the global ref on mObject while the list is being iterated.
+ sp<HwBinderDeathRecipientList> list = mList.promote();
+ if (list != NULL) {
+ AutoMutex _l(list->lock());
+
+ // Demote from strong ref to weak after binderDied() has been delivered,
+ // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
+ mObjectWeak = env->NewWeakGlobalRef(mObject);
+ env->DeleteGlobalRef(mObject);
+ mObject = NULL;
+ }
+ }
+ }
+
+ void clearReference()
+ {
+ sp<HwBinderDeathRecipientList> list = mList.promote();
+ if (list != NULL) {
+ list->remove(this);
+ } else {
+ ALOGE("clearReference() on JDR %p but DRL wp purged", this);
+ }
+ }
+
+ bool matches(jobject obj) {
+ bool result;
+ JNIEnv* env = javavm_to_jnienv(mVM);
+
+ if (mObject != NULL) {
+ result = env->IsSameObject(obj, mObject);
+ } else {
+ jobject me = env->NewLocalRef(mObjectWeak);
+ result = env->IsSameObject(obj, me);
+ env->DeleteLocalRef(me);
+ }
+ return result;
+ }
+
+ void warnIfStillLive() {
+ if (mObject != NULL) {
+ // Okay, something is wrong -- we have a hard reference to a live death
+ // recipient on the VM side, but the list is being torn down.
+ JNIEnv* env = javavm_to_jnienv(mVM);
+ ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
+ ScopedLocalRef<jstring> nameRef(env,
+ (jstring) env->CallObjectMethod(objClassRef.get(), gClassOffsets.mGetName));
+ ScopedUtfChars nameUtf(env, nameRef.get());
+ if (nameUtf.c_str() != NULL) {
+ ALOGW("BinderProxy is being destroyed but the application did not call "
+ "unlinkToDeath to unlink all of its death recipients beforehand. "
+ "Releasing leaked death recipient: %s", nameUtf.c_str());
+ } else {
+ ALOGW("BinderProxy being destroyed; unable to get DR object name");
+ env->ExceptionClear();
+ }
+ }
+ }
+
+protected:
+ virtual ~HwBinderDeathRecipient()
+ {
+ JNIEnv* env = javavm_to_jnienv(mVM);
+ if (mObject != NULL) {
+ env->DeleteGlobalRef(mObject);
+ } else {
+ env->DeleteWeakGlobalRef(mObjectWeak);
+ }
+ }
+
+private:
+ JavaVM* const mVM;
+ jobject mObject;
+ jweak mObjectWeak; // will be a weak ref to the same VM-side DeathRecipient after binderDied()
+ jlong mCookie;
+ wp<HwBinderDeathRecipientList> mList;
+};
+// ----------------------------------------------------------------------------
+
+HwBinderDeathRecipientList::HwBinderDeathRecipientList() {
+}
+
+HwBinderDeathRecipientList::~HwBinderDeathRecipientList() {
+ AutoMutex _l(mLock);
+
+ for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+ deathRecipient->warnIfStillLive();
+ }
+}
+
+void HwBinderDeathRecipientList::add(const sp<HwBinderDeathRecipient>& recipient) {
+ AutoMutex _l(mLock);
+
+ mList.push_back(recipient);
+}
+
+void HwBinderDeathRecipientList::remove(const sp<HwBinderDeathRecipient>& recipient) {
+ AutoMutex _l(mLock);
+
+ List< sp<HwBinderDeathRecipient> >::iterator iter;
+ for (iter = mList.begin(); iter != mList.end(); iter++) {
+ if (*iter == recipient) {
+ mList.erase(iter);
+ return;
+ }
+ }
+}
+
+sp<HwBinderDeathRecipient> HwBinderDeathRecipientList::find(jobject recipient) {
+ AutoMutex _l(mLock);
+
+ for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+ if (deathRecipient->matches(recipient)) {
+ return deathRecipient;
+ }
+ }
+ return NULL;
+}
+
+Mutex& HwBinderDeathRecipientList::lock() {
+ return mLock;
+}
// static
void JHwRemoteBinder::InitClass(JNIEnv *env) {
- ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
+ jclass clazz = FindClassOrDie(env, CLASS_PATH);
- gFields.contextID =
- GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J");
+ gProxyOffsets.proxy_class = MakeGlobalRefOrDie(env, clazz);
+ gProxyOffsets.contextID =
+ GetFieldIDOrDie(env, clazz, "mNativeContext", "J");
+ gProxyOffsets.constructID = GetMethodIDOrDie(env, clazz, "<init>", "()V");
+ gProxyOffsets.sendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+ "(Landroid/os/IHwBinder$DeathRecipient;J)V");
- gFields.constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
+ clazz = FindClassOrDie(env, "java/lang/Class");
+ gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
}
// static
sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(
JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context) {
sp<JHwRemoteBinder> old =
- (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+ (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
if (context != NULL) {
context->incStrong(NULL /* id */);
@@ -67,7 +238,7 @@
old->decStrong(NULL /* id */);
}
- env->SetLongField(thiz, gFields.contextID, (long)context.get());
+ env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get());
return old;
}
@@ -75,7 +246,7 @@
// static
sp<JHwRemoteBinder> JHwRemoteBinder::GetNativeContext(
JNIEnv *env, jobject thiz) {
- return (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+ return (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
}
// static
@@ -84,7 +255,7 @@
ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
// XXX Have to look up the constructor here because otherwise that static
- // class initializer isn't called and gFields.constructID is undefined :(
+ // class initializer isn't called and gProxyOffsets.constructID is undefined :(
jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
@@ -97,6 +268,7 @@
JHwRemoteBinder::JHwRemoteBinder(
JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)
: mBinder(binder) {
+ mDeathRecipientList = new HwBinderDeathRecipientList();
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
@@ -114,7 +286,7 @@
mClass = NULL;
}
-sp<hardware::IBinder> JHwRemoteBinder::getBinder() {
+sp<hardware::IBinder> JHwRemoteBinder::getBinder() const {
return mBinder;
}
@@ -122,6 +294,10 @@
mBinder = binder;
}
+sp<HwBinderDeathRecipientList> JHwRemoteBinder::getDeathRecipientList() const {
+ return mDeathRecipientList;
+}
+
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -174,6 +350,73 @@
signalExceptionForError(env, err);
}
+static jboolean JHwRemoteBinder_linkToDeath(JNIEnv* env, jobject thiz,
+ jobject recipient, jlong cookie)
+{
+ if (recipient == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return JNI_FALSE;
+ }
+
+ sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+ sp<hardware::IBinder> binder = context->getBinder();
+
+ if (!binder->localBinder()) {
+ HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+ sp<HwBinderDeathRecipient> jdr = new HwBinderDeathRecipient(env, recipient, cookie, list);
+ status_t err = binder->linkToDeath(jdr, NULL, 0);
+ if (err != NO_ERROR) {
+ // Failure adding the death recipient, so clear its reference
+ // now.
+ jdr->clearReference();
+ return JNI_FALSE;
+ }
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean JHwRemoteBinder_unlinkToDeath(JNIEnv* env, jobject thiz,
+ jobject recipient)
+{
+ jboolean res = JNI_FALSE;
+ if (recipient == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return res;
+ }
+
+ sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+ sp<hardware::IBinder> binder = context->getBinder();
+
+ if (!binder->localBinder()) {
+ status_t err = NAME_NOT_FOUND;
+
+ // If we find the matching recipient, proceed to unlink using that
+ HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+ sp<HwBinderDeathRecipient> origJDR = list->find(recipient);
+ if (origJDR != NULL) {
+ wp<hardware::IBinder::DeathRecipient> dr;
+ err = binder->unlinkToDeath(origJDR, NULL, 0, &dr);
+ if (err == NO_ERROR && dr != NULL) {
+ sp<hardware::IBinder::DeathRecipient> sdr = dr.promote();
+ HwBinderDeathRecipient* jdr = static_cast<HwBinderDeathRecipient*>(sdr.get());
+ if (jdr != NULL) {
+ jdr->clearReference();
+ }
+ }
+ }
+
+ if (err == NO_ERROR || err == DEAD_OBJECT) {
+ res = JNI_TRUE;
+ } else {
+ jniThrowException(env, "java/util/NoSuchElementException",
+ "Death link does not exist");
+ }
+ }
+
+ return res;
+}
+
static JNINativeMethod gMethods[] = {
{ "native_init", "()J", (void *)JHwRemoteBinder_native_init },
@@ -183,6 +426,14 @@
{ "transact",
"(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
(void *)JHwRemoteBinder_native_transact },
+
+ {"linkToDeath",
+ "(Landroid/os/IHwBinder$DeathRecipient;J)Z",
+ (void*)JHwRemoteBinder_linkToDeath},
+
+ {"unlinkToDeath",
+ "(Landroid/os/IHwBinder$DeathRecipient;)Z",
+ (void*)JHwRemoteBinder_unlinkToDeath},
};
namespace android {
diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h
index fd33338..77a0278 100644
--- a/core/jni/android_os_HwRemoteBinder.h
+++ b/core/jni/android_os_HwRemoteBinder.h
@@ -20,10 +20,33 @@
#include <android-base/macros.h>
#include <hwbinder/Binder.h>
#include <jni.h>
+#include <utils/List.h>
+#include <utils/Mutex.h>
#include <utils/RefBase.h>
namespace android {
+// Per-IBinder death recipient bookkeeping. This is how we reconcile local jobject
+// death recipient references passed in through JNI with the permanent corresponding
+// HwBinderDeathRecipient objects.
+
+class HwBinderDeathRecipient;
+
+class HwBinderDeathRecipientList : public RefBase {
+ List< sp<HwBinderDeathRecipient> > mList;
+ Mutex mLock;
+
+public:
+ HwBinderDeathRecipientList();
+ ~HwBinderDeathRecipientList();
+
+ void add(const sp<HwBinderDeathRecipient>& recipient);
+ void remove(const sp<HwBinderDeathRecipient>& recipient);
+ sp<HwBinderDeathRecipient> find(jobject recipient);
+
+ Mutex& lock(); // Use with care; specifically for mutual exclusion during binder death
+};
+
struct JHwRemoteBinder : public RefBase {
static void InitClass(JNIEnv *env);
@@ -37,8 +60,9 @@
JHwRemoteBinder(
JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder);
- sp<hardware::IBinder> getBinder();
+ sp<hardware::IBinder> getBinder() const;
void setBinder(const sp<hardware::IBinder> &binder);
+ sp<HwBinderDeathRecipientList> getDeathRecipientList() const;
protected:
virtual ~JHwRemoteBinder();
@@ -48,7 +72,7 @@
jobject mObject;
sp<hardware::IBinder> mBinder;
-
+ sp<HwBinderDeathRecipientList> mDeathRecipientList;
DISALLOW_COPY_AND_ASSIGN(JHwRemoteBinder);
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index dfc89fa..612eba7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -345,7 +345,10 @@
@Override
public void onAnnouncementRequested(CharSequence announcement) {
- announceForAccessibility(announcement);
+ if (announcement != null) {
+ mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
+ .sendToTarget();
+ }
}
};
r.tile.addCallback(callback);
@@ -514,10 +517,13 @@
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
+ private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_DETAIL) {
handleShowDetail((Record)msg.obj, msg.arg1 != 0);
+ } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
+ announceForAccessibility((CharSequence)msg.obj);
}
}
}
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index 1b85632..06abca9 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -37,7 +37,7 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
- publishBinderService(ContextHubService.CONTEXTHUB_SERVICE, mContextHubService);
+ publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
}
}
}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 2c94cfb..983d039 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -147,37 +147,39 @@
}
private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
- if (mPackagesToWatch.contains(scorerPackageName)) {
+ if (!mPackagesToWatch.contains(scorerPackageName)) {
+ // Early exit when we don't care about the package that has changed.
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "Evaluating binding for: " + scorerPackageName
+ + ", forceUnbind=" + forceUnbind);
+ }
+ final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+ if (activeScorer == null) {
+ // Package change has invalidated a scorer, this will also unbind any service
+ // connection.
+ if (DBG) Log.d(TAG, "No active scorers available.");
+ unbindFromScoringServiceIfNeeded();
+ } else if (activeScorer.packageName.equals(scorerPackageName)) {
+ // The active scoring service changed in some way.
if (DBG) {
- Log.d(TAG, "Evaluating binding for: " + scorerPackageName
- + ", forceUnbind=" + forceUnbind);
- }
- final NetworkScorerAppData activeScorer =
- mNetworkScorerAppManager.getActiveScorer();
- if (activeScorer == null) {
- // Package change has invalidated a scorer, this will also unbind any service
- // connection.
- if (DBG) Log.d(TAG, "No active scorers available.");
- unbindFromScoringServiceIfNeeded();
- } else if (activeScorer.packageName.equals(scorerPackageName)) {
- // The active scoring service changed in some way.
- if (DBG) {
- Log.d(TAG, "Possible change to the active scorer: "
+ Log.d(TAG, "Possible change to the active scorer: "
+ activeScorer.packageName);
- }
- if (forceUnbind) {
- unbindFromScoringServiceIfNeeded();
- }
- bindToScoringServiceIfNeeded(activeScorer);
- } else {
- // One of the scoring apps on the device has changed and we may no longer be
- // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
- // will sort that out to leave us bound to the most recent active scorer.
- if (DBG) {
- Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
- }
- bindToScoringServiceIfNeeded(activeScorer);
}
+ if (forceUnbind) {
+ unbindFromScoringServiceIfNeeded();
+ }
+ bindToScoringServiceIfNeeded(activeScorer);
+ } else {
+ // One of the scoring apps on the device has changed and we may no longer be
+ // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
+ // will sort that out to leave us bound to the most recent active scorer.
+ if (DBG) {
+ Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
+ }
+ bindToScoringServiceIfNeeded(activeScorer);
}
}
}