blob: cf59a56a13dc09947ef05312f219501ac088710a [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "JHwRemoteBinder"
#include <android-base/logging.h>
#include "android_os_HwRemoteBinder.h"
#include "android_os_HwParcel.h"
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/Status.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedLocalRef.h>
#include "core_jni_helpers.h"
using android::AndroidRuntime;
#define PACKAGE_PATH "android/os"
#define CLASS_NAME "HwRemoteBinder"
#define CLASS_PATH PACKAGE_PATH "/" CLASS_NAME
namespace android {
static struct fields_t {
jclass proxy_class;
jfieldID contextID;
jmethodID constructID;
jmethodID sendDeathNotice;
} gProxyOffsets;
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) {
jclass clazz = FindClassOrDie(env, CLASS_PATH);
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");
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, gProxyOffsets.contextID);
if (context != NULL) {
context->incStrong(NULL /* id */);
}
if (old != NULL) {
old->decStrong(NULL /* id */);
}
env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get());
return old;
}
// static
sp<JHwRemoteBinder> JHwRemoteBinder::GetNativeContext(
JNIEnv *env, jobject thiz) {
return (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
}
// static
jobject JHwRemoteBinder::NewObject(
JNIEnv *env, const sp<hardware::IBinder> &binder) {
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 gProxyOffsets.constructID is undefined :(
jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
jobject obj = env->NewObject(clazz.get(), constructID);
JHwRemoteBinder::GetNativeContext(env, obj)->setBinder(binder);
return obj;
}
JHwRemoteBinder::JHwRemoteBinder(
JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)
: mBinder(binder) {
mDeathRecipientList = new HwBinderDeathRecipientList();
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
mObject = env->NewWeakGlobalRef(thiz);
}
JHwRemoteBinder::~JHwRemoteBinder() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->DeleteWeakGlobalRef(mObject);
mObject = NULL;
}
sp<hardware::IBinder> JHwRemoteBinder::getBinder() const {
return mBinder;
}
void JHwRemoteBinder::setBinder(const sp<hardware::IBinder> &binder) {
mBinder = binder;
}
sp<HwBinderDeathRecipientList> JHwRemoteBinder::getDeathRecipientList() const {
return mDeathRecipientList;
}
} // namespace android
////////////////////////////////////////////////////////////////////////////////
using namespace android;
static void releaseNativeContext(void *nativeContext) {
sp<JHwRemoteBinder> binder = (JHwRemoteBinder *)nativeContext;
if (binder != NULL) {
binder->decStrong(NULL /* id */);
}
}
static jlong JHwRemoteBinder_native_init(JNIEnv *env) {
JHwRemoteBinder::InitClass(env);
return reinterpret_cast<jlong>(&releaseNativeContext);
}
static void JHwRemoteBinder_native_setup_empty(JNIEnv *env, jobject thiz) {
sp<JHwRemoteBinder> context =
new JHwRemoteBinder(env, thiz, NULL /* service */);
JHwRemoteBinder::SetNativeContext(env, thiz, context);
}
static void JHwRemoteBinder_native_transact(
JNIEnv *env,
jobject thiz,
jint code,
jobject requestObj,
jobject replyObj,
jint flags) {
sp<hardware::IBinder> binder =
JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder();
if (requestObj == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return;
}
const hardware::Parcel *request =
JHwParcel::GetNativeContext(env, requestObj)->getParcel();
hardware::Parcel *reply =
JHwParcel::GetNativeContext(env, replyObj)->getParcel();
status_t err = binder->transact(code, *request, reply, flags);
signalExceptionForError(env, err, true /* canThrowRemoteException */);
}
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 },
{ "native_setup_empty", "()V",
(void *)JHwRemoteBinder_native_setup_empty },
{ "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 {
int register_android_os_HwRemoteBinder(JNIEnv *env) {
return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods));
}
} // namespace android