diff options
12 files changed, 846 insertions, 123 deletions
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 1100731702a2..c22f46cdc2b5 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -646,6 +646,37 @@ public final class BinderProxy implements IBinder { private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags); /** + * This list is to hold strong reference to the frozen state callbacks. The callbacks are only + * weakly referenced by JNI so the strong references here are needed to keep the callbacks + * around until the proxy is GC'ed. + */ + private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks = + Collections.synchronizedList(new ArrayList<>()); + + /** + * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)} + */ + public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + throws RemoteException { + addFrozenStateChangeCallbackNative(callback); + mFrozenStateChangeCallbacks.add(callback); + } + + /** + * See {@link IBinder#removeFrozenStateChangeCallback} + */ + public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + mFrozenStateChangeCallbacks.remove(callback); + return removeFrozenStateChangeCallbackNative(callback); + } + + private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback) + throws RemoteException; + + private native boolean removeFrozenStateChangeCallbackNative( + IFrozenStateChangeCallback callback); + + /** * Perform a dump on the remote object * * @param fd The raw file descriptor that the dump is being sent to. @@ -730,6 +761,17 @@ public final class BinderProxy implements IBinder { } } + private static void invokeFrozenStateChangeCallback( + IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) { + try { + callback.onFrozenStateChanged(binderProxy, + IFrozenStateChangeCallback.State.values()[stateIndex]); + } catch (RuntimeException exc) { + Log.w("BinderNative", "Uncaught exception from frozen state change callback", + exc); + } + } + /** * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the * native IBinder object, and a DeathRecipientList. diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 50242bad191b..8185e8e542e1 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -376,4 +376,53 @@ public interface IBinder { * return value instead. */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags); + + /** @hide */ + interface IFrozenStateChangeCallback { + enum State {FROZEN, UNFROZEN}; + + /** + * Interface for receiving a callback when the process hosting an IBinder + * has changed its frozen state. + * @param who The IBinder whose hosting process has changed state. + * @param state The latest state. + */ + void onFrozenStateChanged(@NonNull IBinder who, State state); + } + + /** + * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process + * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is + * invoked with the latest process frozen state. + * + * <p>If the listener process (the one using this API) is itself frozen, state change events + * might be combined into a single one with the latest frozen state. This single event would + * then be delivered when the listener process becomes unfrozen. Similarly, if an event happens + * before the previous event is consumed, they might be combined. This means the callback might + * not be called for every single state change, so don't rely on this API to count how many + * times the state has changed.</p> + * + * <p>The callback is automatically removed when all references to the binder proxy are + * dropped.</p> + * + * <p>You will only receive state change notifications for remote binders, as local binders by + * definition can't be frozen without you being frozen too.</p> + * + * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support + * this feature. + * @hide + */ + default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + /** + * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when + * the hosting process changes its frozen state. + * @hide + */ + default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) { + throw new UnsupportedOperationException(); + } } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 46b4695a9cec..921b77d61f4d 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -56,11 +56,11 @@ //#undef ALOGV //#define ALOGV(...) fprintf(stderr, __VA_ARGS__) -#define DEBUG_DEATH 0 -#if DEBUG_DEATH -#define LOGDEATH ALOGD +#define DEBUG_DEATH_FREEZE 0 +#if DEBUG_DEATH_FREEZE +#define LOG_DEATH_FREEZE ALOGD #else -#define LOGDEATH ALOGV +#define LOG_DEATH_FREEZE ALOGV #endif using namespace android; @@ -116,6 +116,7 @@ static struct binderproxy_offsets_t jclass mClass; jmethodID mGetInstance; jmethodID mSendDeathNotice; + jmethodID mInvokeFrozenStateChangeCallback; // Object state. jfieldID mNativeData; // Field holds native pointer to BinderProxyNativeData. @@ -547,23 +548,59 @@ private: // ---------------------------------------------------------------------------- -// Per-IBinder death recipient bookkeeping. This is how we reconcile local jobject -// death recipient references passed in through JNI with the permanent corresponding -// JavaDeathRecipient objects. - -class JavaDeathRecipient; - -class DeathRecipientList : public RefBase { - List< sp<JavaDeathRecipient> > mList; +// A JavaRecipient receives either death notifications or frozen state change +// callbacks from natve code (IBinder) and dispatch the notifications to its +// corresponding Java listener object. +// +// A RecipientList keeps tracks of all JavaRecipients for an IBinder. This way +// we can find a JavaRecipient given a Java listener object. +// +// The implementation is shared between death recipients and frozen state change +// callbacks via template. For death recipients the template is instantiated as +// follows: +// +// IBinder::DeathRecipient +// ^ +// | +// (inherits) +// | +// JavaRecipient<IBinder::DeathRecipient> <----> RecipientList<IBinder::DeathRecipient> +// ^ +// | +// (inherits) +// | +// JavaDeathRecipient +// +// +// The instantiation for frozen state change callbacks are: +// +// IBinder::FrozenStateChangeCallback +// ^ +// | +// (inherits) +// | +// JavaRecipient<IBinder::FrozenStateChangeCallback> +// ^ ^ +// | | +// (inherits) +--> RecipientList<IBinder::FrozenStateChangeCallback> +// | +// JavaFrozenStateChangeCallback + +template <typename T> +class JavaRecipient; + +template <typename T> +class RecipientList : public RefBase { + List<sp<JavaRecipient<T> > > mList; Mutex mLock; public: - DeathRecipientList(); - ~DeathRecipientList(); + RecipientList(); + ~RecipientList(); - void add(const sp<JavaDeathRecipient>& recipient); - void remove(const sp<JavaDeathRecipient>& recipient); - sp<JavaDeathRecipient> find(jobject recipient); + void add(const sp<JavaRecipient<T> >& recipient); + void remove(const sp<JavaRecipient<T> >& recipient); + sp<JavaRecipient<T> > find(jobject recipient); Mutex& lock(); // Use with care; specifically for mutual exclusion during binder death }; @@ -584,11 +621,113 @@ static constexpr bool target_sdk_is_at_least_vic() { #endif // __BIONIC__ #endif // BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI -class JavaDeathRecipient : public IBinder::DeathRecipient -{ +template <typename T> +constexpr const char* logPrefix(); + +template <> +constexpr const char* logPrefix<IBinder::DeathRecipient>() { + return "[DEATH]"; +} + +template <> +constexpr const char* logPrefix<IBinder::FrozenStateChangeCallback>() { + return "[FREEZE]"; +} + +template <typename T> +class JavaRecipient : public T { public: - JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list) + JavaRecipient(JNIEnv* env, jobject object, const sp<RecipientList<T> >& list, + bool useWeakReference) : mVM(jnienv_to_javavm(env)), mObject(NULL), mObjectWeak(NULL), mList(list) { + if (useWeakReference) { + mObjectWeak = env->NewWeakGlobalRef(object); + } else { + mObject = env->NewGlobalRef(object); + } + // These objects manage their own lifetimes so are responsible for final bookkeeping. + // The list holds a strong reference to this object. + LOG_DEATH_FREEZE("%s Adding JavaRecipient %p to RecipientList %p", logPrefix<T>(), this, + list.get()); + list->add(this); + } + + void clearReference() { + sp<RecipientList<T> > list = mList.promote(); + if (list != NULL) { + LOG_DEATH_FREEZE("%s Removing JavaRecipient %p from RecipientList %p", logPrefix<T>(), + this, list.get()); + list->remove(this); + } else { + LOG_DEATH_FREEZE("%s clearReference() on JavaRecipient %p but RecipientList wp purged", + logPrefix<T>(), this); + } + } + + bool matches(jobject obj) { + bool result; + JNIEnv* env = javavm_to_jnienv(mVM); + + if (mObject != NULL) { + result = env->IsSameObject(obj, mObject); + } else { + ScopedLocalRef<jobject> me(env, env->NewLocalRef(mObjectWeak)); + result = env->IsSameObject(obj, me.get()); + } + 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 ~JavaRecipient() { + // ALOGI("Removing death ref: recipient=%p\n", mObject); + JNIEnv* env = javavm_to_jnienv(mVM); + if (mObject != NULL) { + env->DeleteGlobalRef(mObject); + } else { + env->DeleteWeakGlobalRef(mObjectWeak); + } + } + + JavaVM* const mVM; + + // If useWeakReference is false (e.g. JavaDeathRecipient when target sdk version < 35), the + // Java-side Recipient is strongly referenced from mObject initially, and may later be demoted + // to a weak reference (mObjectWeak), e.g. upon linkToDeath() and then after binderDied() is + // called. + // If useWeakReference is true, the strong reference is never made here (i.e. mObject == NULL + // always). Instead, the strong reference to the Java-side Recipient is made in + // BinderProxy.{mDeathRecipients,mFrozenStateChangeCallbacks}. In the native world, only the + // weak reference is kept. + jobject mObject; + jweak mObjectWeak; + wp<RecipientList<T> > mList; +}; + +class JavaDeathRecipient : public JavaRecipient<IBinder::DeathRecipient> { +public: + static bool useWeakReference() { // b/298374304: For apps targeting Android V or beyond, we no longer hold the global JNI ref // to the death recipient objects. This is to prevent the memory leak which can happen when // the death recipient object internally has a strong reference to the proxy object. Under @@ -604,25 +743,26 @@ public: // reference to. If however you want to get binderDied() regardless of the proxy object's // lifecycle, keep a strong reference to the death recipient object by yourself. #ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI - if (target_sdk_is_at_least_vic()) { - mObjectWeak = env->NewWeakGlobalRef(object); - } else + return target_sdk_is_at_least_vic(); +#else + return false; #endif - { - mObject = env->NewGlobalRef(object); - } - // These objects manage their own lifetimes so are responsible for final bookkeeping. - // The list holds a strong reference to this object. - LOGDEATH("Adding JDR %p to DRL %p", this, list.get()); - list->add(this); + } + JavaDeathRecipient(JNIEnv* env, jobject object, + const sp<RecipientList<IBinder::DeathRecipient> >& list) + : JavaRecipient(env, object, list, useWeakReference()) { gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed); gcIfManyNewRefs(env); } + ~JavaDeathRecipient() { + gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed); + } + void binderDied(const wp<IBinder>& who) { - LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); + LOG_DEATH_FREEZE("Receiving binderDied() on JavaDeathRecipient %p\n", this); if (mObject == NULL && mObjectWeak == NULL) { return; } @@ -662,7 +802,7 @@ public: // with our containing DeathRecipientList so that we can't delete the global ref on mObject // while the list is being iterated. if (mObject != NULL) { - sp<DeathRecipientList> list = mList.promote(); + auto list = mList.promote(); if (list != NULL) { AutoMutex _l(list->lock()); @@ -673,126 +813,96 @@ public: } } - void clearReference() - { - sp<DeathRecipientList> list = mList.promote(); - if (list != NULL) { - LOGDEATH("Removing JDR %p from DRL %p", this, list.get()); - list->remove(this); - } else { - LOGDEATH("clearReference() on JDR %p but DRL wp purged", this); - } - } - - bool matches(jobject obj) { - bool result; - JNIEnv* env = javavm_to_jnienv(mVM); +private: + // Whether binderDied was called or not. + bool mFired = false; +}; - if (mObject != NULL) { - result = env->IsSameObject(obj, mObject); - } else { - ScopedLocalRef<jobject> me(env, env->NewLocalRef(mObjectWeak)); - result = env->IsSameObject(obj, me.get()); +class JavaFrozenStateChangeCallback : public JavaRecipient<IBinder::FrozenStateChangeCallback> { +public: + JavaFrozenStateChangeCallback( + JNIEnv* env, jobject object, + const sp<RecipientList<IBinder::FrozenStateChangeCallback> >& list) + : JavaRecipient(env, object, list, /*useWeakReference=*/true) {} + + void onStateChanged(const wp<IBinder>& who, State state) { + LOG_DEATH_FREEZE("Receiving onStateChanged() on JavaFrozenStateChangeCallback %p. state: " + "%s\n", + this, state == State::FROZEN ? "FROZEN" : "UNFROZEN"); + if (mObjectWeak == NULL) { + return; } - return result; - } + JNIEnv* env = javavm_to_jnienv(mVM); + ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote())); - 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(); - } + // Hold a local reference to the recipient. This may fail if the recipient is weakly + // referenced, in which case we can't deliver the notification. + ScopedLocalRef<jobject> jCallback(env, env->NewLocalRef(mObjectWeak)); + if (jCallback.get() == NULL) { + return; } - } - -protected: - virtual ~JavaDeathRecipient() - { - //ALOGI("Removing death ref: recipient=%p\n", mObject); - gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed); - JNIEnv* env = javavm_to_jnienv(mVM); - if (mObject != NULL) { - env->DeleteGlobalRef(mObject); - } else { - env->DeleteWeakGlobalRef(mObjectWeak); + env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, + gBinderProxyOffsets.mInvokeFrozenStateChangeCallback, + jCallback.get(), jBinderProxy.get(), state); + if (env->ExceptionCheck()) { + jthrowable excep = env->ExceptionOccurred(); + binder_report_exception(env, excep, + "*** Uncaught exception returned from frozen state change " + "notification!"); } } - -private: - JavaVM* const mVM; - - // If target sdk version < 35, the Java-side DeathRecipient is strongly referenced from mObject - // upon linkToDeath() and then after binderDied() is called, the strong reference is demoted to - // a weak reference (mObjectWeak). - // If target sdk version >= 35, the strong reference is never made here (i.e. mObject == NULL - // always). Instead, the strong reference to the Java-side DeathRecipient is made in - // BinderProxy.mDeathRecipients. In the native world, only the weak reference is kept. - jobject mObject; - jweak mObjectWeak; - wp<DeathRecipientList> mList; - - // Whether binderDied was called or not. - bool mFired = false; }; // ---------------------------------------------------------------------------- -DeathRecipientList::DeathRecipientList() { - LOGDEATH("New DRL @ %p", this); +template <typename T> +RecipientList<T>::RecipientList() { + LOG_DEATH_FREEZE("%s New RecipientList @ %p", logPrefix<T>(), this); } -DeathRecipientList::~DeathRecipientList() { - LOGDEATH("Destroy DRL @ %p", this); +template <typename T> +RecipientList<T>::~RecipientList() { + LOG_DEATH_FREEZE("%s Destroy RecipientList @ %p", logPrefix<T>(), this); AutoMutex _l(mLock); - // Should never happen -- the JavaDeathRecipient objects that have added themselves + // Should never happen -- the JavaRecipientList objects that have added themselves // to the list are holding references on the list object. Only when they are torn // down can the list header be destroyed. if (mList.size() > 0) { - List< sp<JavaDeathRecipient> >::iterator iter; - for (iter = mList.begin(); iter != mList.end(); iter++) { + for (auto iter = mList.begin(); iter != mList.end(); iter++) { (*iter)->warnIfStillLive(); } } } -void DeathRecipientList::add(const sp<JavaDeathRecipient>& recipient) { +template <typename T> +void RecipientList<T>::add(const sp<JavaRecipient<T> >& recipient) { AutoMutex _l(mLock); - LOGDEATH("DRL @ %p : add JDR %p", this, recipient.get()); + LOG_DEATH_FREEZE("%s RecipientList @ %p : add JavaRecipient %p", logPrefix<T>(), this, + recipient.get()); mList.push_back(recipient); } -void DeathRecipientList::remove(const sp<JavaDeathRecipient>& recipient) { +template <typename T> +void RecipientList<T>::remove(const sp<JavaRecipient<T> >& recipient) { AutoMutex _l(mLock); - List< sp<JavaDeathRecipient> >::iterator iter; - for (iter = mList.begin(); iter != mList.end(); iter++) { + for (auto iter = mList.begin(); iter != mList.end(); iter++) { if (*iter == recipient) { - LOGDEATH("DRL @ %p : remove JDR %p", this, recipient.get()); + LOG_DEATH_FREEZE("%s RecipientList @ %p : remove JavaRecipient %p", logPrefix<T>(), + this, recipient.get()); mList.erase(iter); return; } } } -sp<JavaDeathRecipient> DeathRecipientList::find(jobject recipient) { +template <typename T> +sp<JavaRecipient<T> > RecipientList<T>::find(jobject recipient) { AutoMutex _l(mLock); - List< sp<JavaDeathRecipient> >::iterator iter; - for (iter = mList.begin(); iter != mList.end(); iter++) { + for (auto iter = mList.begin(); iter != mList.end(); iter++) { if ((*iter)->matches(recipient)) { return *iter; } @@ -800,10 +910,14 @@ sp<JavaDeathRecipient> DeathRecipientList::find(jobject recipient) { return NULL; } -Mutex& DeathRecipientList::lock() { +template <typename T> +Mutex& RecipientList<T>::lock() { return mLock; } +using DeathRecipientList = RecipientList<IBinder::DeathRecipient>; +using FrozenStateChangeCallbackList = RecipientList<IBinder::FrozenStateChangeCallback>; + // ---------------------------------------------------------------------------- namespace android { @@ -821,6 +935,11 @@ struct BinderProxyNativeData { // Death recipients for mObject. Reference counted only because DeathRecipients // hold a weak reference that can be temporarily promoted. sp<DeathRecipientList> mOrgue; // Death recipients for mObject. + + // Frozen state change callbacks for mObject. Reference counted only because + // JavaFrozenStateChangeCallback hold a weak reference that can be + // temporarily promoted. + sp<FrozenStateChangeCallbackList> mFrozenStateChangCallbackList; }; BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) { @@ -840,12 +959,13 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) if (val->checkSubclass(&gBinderOffsets)) { // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object. jobject object = static_cast<JavaBBinder*>(val.get())->object(); - LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object); + LOG_DEATH_FREEZE("objectForBinder %p: it's our own %p!\n", val.get(), object); return object; } BinderProxyNativeData* nativeData = new BinderProxyNativeData(); nativeData->mOrgue = new DeathRecipientList; + nativeData->mFrozenStateChangCallbackList = new FrozenStateChangeCallbackList; nativeData->mObject = val; jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass, @@ -1448,7 +1568,7 @@ static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj, BinderProxyNativeData *nd = getBPNativeData(env, obj); IBinder* target = nd->mObject.get(); - LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient); + LOG_DEATH_FREEZE("linkToDeath: binder=%p recipient=%p\n", target, recipient); if (!target->localBinder()) { DeathRecipientList* list = nd->mOrgue.get(); @@ -1479,15 +1599,15 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, return JNI_FALSE; } - LOGDEATH("unlinkToDeath: binder=%p recipient=%p\n", target, recipient); + LOG_DEATH_FREEZE("unlinkToDeath: binder=%p recipient=%p\n", target, recipient); if (!target->localBinder()) { status_t err = NAME_NOT_FOUND; // If we find the matching recipient, proceed to unlink using that DeathRecipientList* list = nd->mOrgue.get(); - sp<JavaDeathRecipient> origJDR = list->find(recipient); - LOGDEATH(" unlink found list %p and JDR %p", list, origJDR.get()); + sp<JavaRecipient<IBinder::DeathRecipient> > origJDR = list->find(recipient); + LOG_DEATH_FREEZE(" unlink found list %p and JDR %p", list, origJDR.get()); if (origJDR != NULL) { wp<IBinder::DeathRecipient> dr; err = target->unlinkToDeath(origJDR, NULL, flags, &dr); @@ -1513,11 +1633,85 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, return res; } +static void android_os_BinderProxy_addFrozenStateChangeCallback( + JNIEnv* env, jobject obj, + jobject callback) // throws RemoteException +{ + if (callback == NULL) { + jniThrowNullPointerException(env, NULL); + return; + } + + BinderProxyNativeData* nd = getBPNativeData(env, obj); + IBinder* target = nd->mObject.get(); + + LOG_DEATH_FREEZE("addFrozenStateChangeCallback: binder=%p callback=%p\n", target, callback); + + if (!target->localBinder()) { + FrozenStateChangeCallbackList* list = nd->mFrozenStateChangCallbackList.get(); + auto jfscc = sp<JavaFrozenStateChangeCallback>::make(env, callback, list); + status_t err = target->addFrozenStateChangeCallback(jfscc); + if (err != NO_ERROR) { + // Failure adding the callback, so clear its reference now. + jfscc->clearReference(); + signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/); + } + } +} + +static jboolean android_os_BinderProxy_removeFrozenStateChangeCallback(JNIEnv* env, jobject obj, + jobject callback) { + jboolean res = JNI_FALSE; + if (callback == NULL) { + jniThrowNullPointerException(env, NULL); + return res; + } + + BinderProxyNativeData* nd = getBPNativeData(env, obj); + IBinder* target = nd->mObject.get(); + if (target == NULL) { + ALOGW("Binder has been finalized when calling removeFrozenStateChangeCallback() with " + "callback=%p)\n", + callback); + return JNI_FALSE; + } + + LOG_DEATH_FREEZE("removeFrozenStateChangeCallback: binder=%p callback=%p\n", target, callback); + + if (!target->localBinder()) { + status_t err = NAME_NOT_FOUND; + + // If we find the matching callback, proceed to unlink using that + FrozenStateChangeCallbackList* list = nd->mFrozenStateChangCallbackList.get(); + sp<JavaRecipient<IBinder::FrozenStateChangeCallback> > origJFSCC = list->find(callback); + LOG_DEATH_FREEZE(" removeFrozenStateChangeCallback found list %p and JFSCC %p", list, + origJFSCC.get()); + if (origJFSCC != NULL) { + err = target->removeFrozenStateChangeCallback(origJFSCC); + if (err == NO_ERROR) { + origJFSCC->clearReference(); + } + } + + if (err == NO_ERROR || err == DEAD_OBJECT) { + res = JNI_TRUE; + } else { + jniThrowException(env, "java/util/NoSuchElementException", + base::StringPrintf("Frozen state change callback does not exist (%s)", + statusToString(err).c_str()) + .c_str()); + } + } + + return res; +} + static void BinderProxy_destroy(void* rawNativeData) { BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData; - LOGDEATH("Destroying BinderProxy: binder=%p drl=%p\n", - nativeData->mObject.get(), nativeData->mOrgue.get()); + LOG_DEATH_FREEZE("Destroying BinderProxy: binder=%p drl=%p fsccl=%p\n", + nativeData->mObject.get(), nativeData->mOrgue.get(), + nativeData->mFrozenStateChangCallbackList.get()); delete nativeData; IPCThreadState::self()->flushCommands(); } @@ -1552,6 +1746,10 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}, {"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, + {"addFrozenStateChangeCallbackNative", + "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback}, + {"removeFrozenStateChangeCallbackNative", + "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; @@ -1574,6 +1772,10 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V"); + gBinderProxyOffsets.mInvokeFrozenStateChangeCallback = + GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback", + "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/" + "IBinder;I)V"); gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); clazz = FindClassOrDie(env, "java/lang/Class"); diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 5793bbe306f1..e7eb9dedab0e 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -21,6 +21,7 @@ filegroup { srcs: [ "DisabledTestApp/src/**/*.java", "EnabledTestApp/src/**/*.java", + "BinderFrozenStateChangeCallbackTestApp/src/**/*.java", "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", "BinderDeathRecipientHelperApp/src/**/*.java", @@ -137,6 +138,7 @@ android_test { ":BinderDeathRecipientHelperApp1", ":BinderDeathRecipientHelperApp2", ":com.android.cts.helpers.aosp", + ":BinderFrozenStateChangeCallbackTestApp", ":BinderProxyCountingTestApp", ":BinderProxyCountingTestService", ], diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index bf2a5b875dba..3fdd72935217 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -22,6 +22,7 @@ <option name="test-file-name" value="FrameworksCoreTests.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> + <option name="test-file-name" value="BinderFrozenStateChangeCallbackTestApp.apk" /> <option name="test-file-name" value="BinderProxyCountingTestApp.apk" /> <option name="test-file-name" value="BinderProxyCountingTestService.apk" /> </target_preparer> diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp new file mode 100644 index 000000000000..de97ddae6b18 --- /dev/null +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2024 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "BinderFrozenStateChangeCallbackTestApp", + + static_libs: ["coretests-aidl"], + srcs: ["**/*.java"], + + platform_apis: true, + certificate: "platform", + + test_suites: ["device-tests"], +} diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..29c8f5587f3a --- /dev/null +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.bfscctestapp"> + + <application> + <service android:name=".BfsccTestAppCmdService" + android:exported="true"/> + </application> +</manifest> diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java new file mode 100644 index 000000000000..77e8a404a0ff --- /dev/null +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.frameworks.coretests.bfscctestapp; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.frameworks.coretests.aidl.IBfsccTestAppCmdService; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class BfsccTestAppCmdService extends Service { + private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() { + private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications = + new LinkedBlockingQueue<>(); + + @Override + public void listenTo(IBinder binder) throws RemoteException { + binder.addFrozenStateChangeCallback( + (IBinder who, IBinder.IFrozenStateChangeCallback.State state) + -> mNotifications.offer(state)); + } + + @Override + public boolean[] waitAndConsumeNotifications() { + List<Boolean> results = new ArrayList<>(); + try { + IBinder.IFrozenStateChangeCallback.State state = + mNotifications.poll(5, TimeUnit.SECONDS); + if (state != null) { + results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + } + } catch (InterruptedException e) { + return null; + } + while (mNotifications.size() > 0) { + results.add(mNotifications.poll() + == IBinder.IFrozenStateChangeCallback.State.FROZEN); + } + boolean[] convertedResults = new boolean[results.size()]; + for (int i = 0; i < results.size(); i++) { + convertedResults[i] = results.get(i).booleanValue(); + } + return convertedResults; + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java index 41b4c69232f4..09d79a69476c 100644 --- a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java +++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java @@ -50,4 +50,4 @@ public class BinderProxyCountingService extends Service { public IBinder onBind(Intent intent) { return mBinder; } -}
\ No newline at end of file +} diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl new file mode 100644 index 000000000000..d8d7dc4b72db --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.frameworks.coretests.aidl; + +interface IBfsccTestAppCmdService { + void listenTo(IBinder binder); + boolean[] waitAndConsumeNotifications(); +} diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java new file mode 100644 index 000000000000..ee2e7e06081e --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2024 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. + */ + +package android.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.UiDevice; + +import com.android.frameworks.coretests.aidl.IBfsccTestAppCmdService; +import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Queue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}. + */ +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) +public class BinderFrozenStateChangeNotificationTest { + private static final String TAG = BinderFrozenStateChangeNotificationTest.class.getSimpleName(); + + private static final String TEST_PACKAGE_NAME_1 = + "com.android.frameworks.coretests.bfscctestapp"; + private static final String TEST_PACKAGE_NAME_2 = + "com.android.frameworks.coretests.bdr_helper_app1"; + private static final String TEST_APP_CMD_SERVICE = + TEST_PACKAGE_NAME_1 + ".BfsccTestAppCmdService"; + + private static final int CALLBACK_WAIT_TIMEOUT_SECS = 5; + + private IBfsccTestAppCmdService mBfsccTestAppCmdService; + private ServiceConnection mTestAppConnection; + private Context mContext; + private Handler mHandler; + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mHandler = new Handler(Looper.getMainLooper()); + ((ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid( + mContext.getPackageManager().getPackageUid(TEST_PACKAGE_NAME_1, 0), + "Wiping Test Package"); + mTestAppConnection = bindService(); + } + + private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException { + final CountDownLatch resultLatch = new CountDownLatch(1); + final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED); + final AtomicReference<Bundle> resultExtras = new AtomicReference<>(); + + final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER) + .setClassName(testPackage, TestCommsReceiver.class.getName()); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + resultCode.set(getResultCode()); + resultExtras.set(getResultExtras(true)); + resultLatch.countDown(); + } + }, mHandler, Activity.RESULT_CANCELED, null, null); + + assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS)); + assertEquals(Activity.RESULT_OK, resultCode.get()); + return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER); + } + + private ServiceConnection bindService() + throws Exception { + final CountDownLatch bindLatch = new CountDownLatch(1); + ServiceConnection connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Service connected"); + mBfsccTestAppCmdService = IBfsccTestAppCmdService.Stub.asInterface(service); + bindLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Service disconnected"); + } + }; + mContext.bindService( + new Intent().setComponent( + new ComponentName(TEST_PACKAGE_NAME_1, TEST_APP_CMD_SERVICE)), + connection, + Context.BIND_AUTO_CREATE + | Context.BIND_NOT_FOREGROUND); + if (!bindLatch.await(5, TimeUnit.SECONDS)) { + fail("Timed out waiting for the service to bind"); + } + return connection; + } + + private void unbindService(ServiceConnection service) { + if (service != null) { + mContext.unbindService(service); + } + } + + @Test + public void onStateChangeCalled() throws Exception { + final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>(); + if (createCallback(mBfsccTestAppCmdService.asBinder(), results) == null) { + return; + } + ensureUnfrozenCallback(results); + freezeApp1(); + ensureFrozenCallback(results); + unfreezeApp1(); + ensureUnfrozenCallback(results); + } + + @Test + public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception { + final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>(); + IBinder.IFrozenStateChangeCallback callback; + if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) { + return; + } + ensureUnfrozenCallback(results); + mBfsccTestAppCmdService.asBinder().removeFrozenStateChangeCallback(callback); + freezeApp1(); + assertEquals("No more callbacks should be invoked.", 0, results.size()); + } + + @Test + public void multipleCallbacks() throws Exception { + final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>(); + final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>(); + IBinder.IFrozenStateChangeCallback callback1; + if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) { + return; + } + ensureUnfrozenCallback(results1); + freezeApp1(); + ensureFrozenCallback(results1); + if (createCallback(mBfsccTestAppCmdService.asBinder(), results2) == null) { + return; + } + ensureFrozenCallback(results2); + + unfreezeApp1(); + ensureUnfrozenCallback(results1); + ensureUnfrozenCallback(results2); + + mBfsccTestAppCmdService.asBinder().removeFrozenStateChangeCallback(callback1); + freezeApp1(); + assertEquals("No more callbacks should be invoked.", 0, results1.size()); + ensureFrozenCallback(results2); + } + + @Test + public void onStateChangeCalledWithTheRightBinder() throws Exception { + final IBinder binder = mBfsccTestAppCmdService.asBinder(); + final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>(); + IBinder.IFrozenStateChangeCallback callback = + (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who); + try { + binder.addFrozenStateChangeCallback(callback); + } catch (UnsupportedOperationException e) { + return; + } + assertEquals("Callback received the wrong Binder object.", + binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS)); + freezeApp1(); + assertEquals("Callback received the wrong Binder object.", + binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS)); + unfreezeApp1(); + assertEquals("Callback received the wrong Binder object.", + binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS)); + } + + @After + public void tearDown() { + if (mTestAppConnection != null) { + mContext.unbindService(mTestAppConnection); + } + } + + private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue) + throws RemoteException { + try { + final IBinder.IFrozenStateChangeCallback callback = + (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> + queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN); + binder.addFrozenStateChangeCallback(callback); + return callback; + } catch (UnsupportedOperationException e) { + return null; + } + } + + private void ensureFrozenCallback(LinkedBlockingQueue<Boolean> queue) + throws InterruptedException { + assertEquals(Boolean.TRUE, queue.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS)); + } + + private void ensureUnfrozenCallback(LinkedBlockingQueue<Boolean> queue) + throws InterruptedException { + assertEquals(Boolean.FALSE, queue.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS)); + } + + private String executeShellCommand(String cmd) throws Exception { + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); + } + + private void freezeApp1() throws Exception { + executeShellCommand("am freeze " + TEST_PACKAGE_NAME_1); + } + + private void freezeApp2() throws Exception { + executeShellCommand("am freeze " + TEST_PACKAGE_NAME_2); + } + + private void unfreezeApp1() throws Exception { + executeShellCommand("am unfreeze " + TEST_PACKAGE_NAME_1); + } + + private void unfreezeApp2() throws Exception { + executeShellCommand("am unfreeze " + TEST_PACKAGE_NAME_2); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java index 27398eae2b8b..d12cba00f0dc 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -124,6 +124,16 @@ public class BinderDeathDispatcherTest { return this; } + @Override + public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback) + throws RemoteException { + } + + @Override + public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) { + return false; + } + public void die() { isAlive = false; if (mRecipient != null) { |