1108 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			1108 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2010 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.app;
 | |
| 
 | |
| import com.android.internal.util.ArrayUtils;
 | |
| 
 | |
| import android.content.BroadcastReceiver;
 | |
| import android.content.ComponentName;
 | |
| import android.content.Context;
 | |
| import android.content.IIntentReceiver;
 | |
| import android.content.Intent;
 | |
| import android.content.ServiceConnection;
 | |
| import android.content.pm.ApplicationInfo;
 | |
| import android.content.pm.IPackageManager;
 | |
| import android.content.pm.PackageManager;
 | |
| import android.content.res.AssetManager;
 | |
| import android.content.res.CompatibilityInfo;
 | |
| import android.content.res.Resources;
 | |
| import android.os.Bundle;
 | |
| import android.os.Handler;
 | |
| import android.os.IBinder;
 | |
| import android.os.Process;
 | |
| import android.os.RemoteException;
 | |
| import android.util.AndroidRuntimeException;
 | |
| import android.util.Slog;
 | |
| 
 | |
| import java.io.File;
 | |
| import java.io.IOException;
 | |
| import java.io.InputStream;
 | |
| import java.lang.ref.WeakReference;
 | |
| import java.net.URL;
 | |
| import java.util.Enumeration;
 | |
| import java.util.HashMap;
 | |
| import java.util.Iterator;
 | |
| 
 | |
| final class IntentReceiverLeaked extends AndroidRuntimeException {
 | |
|     public IntentReceiverLeaked(String msg) {
 | |
|         super(msg);
 | |
|     }
 | |
| }
 | |
| 
 | |
| final class ServiceConnectionLeaked extends AndroidRuntimeException {
 | |
|     public ServiceConnectionLeaked(String msg) {
 | |
|         super(msg);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Local state maintained about a currently loaded .apk.
 | |
|  * @hide
 | |
|  */
 | |
| final class LoadedApk {
 | |
| 
 | |
|     private final ActivityThread mActivityThread;
 | |
|     private final ApplicationInfo mApplicationInfo;
 | |
|     final String mPackageName;
 | |
|     private final String mAppDir;
 | |
|     private final String mResDir;
 | |
|     private final String[] mSharedLibraries;
 | |
|     private final String mDataDir;
 | |
|     private final String mLibDir;
 | |
|     private final File mDataDirFile;
 | |
|     private final ClassLoader mBaseClassLoader;
 | |
|     private final boolean mSecurityViolation;
 | |
|     private final boolean mIncludeCode;
 | |
|     Resources mResources;
 | |
|     private ClassLoader mClassLoader;
 | |
|     private Application mApplication;
 | |
|     CompatibilityInfo mCompatibilityInfo;
 | |
| 
 | |
|     private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers
 | |
|         = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
 | |
|     private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
 | |
|     = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
 | |
|     private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
 | |
|         = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
 | |
|     private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
 | |
|         = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
 | |
| 
 | |
|     int mClientCount = 0;
 | |
| 
 | |
|     Application getApplication() {
 | |
|         return mApplication;
 | |
|     }
 | |
| 
 | |
|     public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
 | |
|             ActivityThread mainThread, ClassLoader baseLoader,
 | |
|             boolean securityViolation, boolean includeCode) {
 | |
|         mActivityThread = activityThread;
 | |
|         mApplicationInfo = aInfo;
 | |
|         mPackageName = aInfo.packageName;
 | |
|         mAppDir = aInfo.sourceDir;
 | |
|         mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir
 | |
|                 : aInfo.publicSourceDir;
 | |
|         mSharedLibraries = aInfo.sharedLibraryFiles;
 | |
|         mDataDir = aInfo.dataDir;
 | |
|         mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
 | |
|         mLibDir = aInfo.nativeLibraryDir;
 | |
|         mBaseClassLoader = baseLoader;
 | |
|         mSecurityViolation = securityViolation;
 | |
|         mIncludeCode = includeCode;
 | |
|         mCompatibilityInfo = new CompatibilityInfo(aInfo);
 | |
| 
 | |
|         if (mAppDir == null) {
 | |
|             if (ActivityThread.mSystemContext == null) {
 | |
|                 ActivityThread.mSystemContext =
 | |
|                     ContextImpl.createSystemContext(mainThread);
 | |
|                 ActivityThread.mSystemContext.getResources().updateConfiguration(
 | |
|                          mainThread.getConfiguration(),
 | |
|                          mainThread.getDisplayMetricsLocked(false));
 | |
|                 //Slog.i(TAG, "Created system resources "
 | |
|                 //        + mSystemContext.getResources() + ": "
 | |
|                 //        + mSystemContext.getResources().getConfiguration());
 | |
|             }
 | |
|             mClassLoader = ActivityThread.mSystemContext.getClassLoader();
 | |
|             mResources = ActivityThread.mSystemContext.getResources();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public LoadedApk(ActivityThread activityThread, String name,
 | |
|             Context systemContext, ApplicationInfo info) {
 | |
|         mActivityThread = activityThread;
 | |
|         mApplicationInfo = info != null ? info : new ApplicationInfo();
 | |
|         mApplicationInfo.packageName = name;
 | |
|         mPackageName = name;
 | |
|         mAppDir = null;
 | |
|         mResDir = null;
 | |
|         mSharedLibraries = null;
 | |
|         mDataDir = null;
 | |
|         mDataDirFile = null;
 | |
|         mLibDir = null;
 | |
|         mBaseClassLoader = null;
 | |
|         mSecurityViolation = false;
 | |
|         mIncludeCode = true;
 | |
|         mClassLoader = systemContext.getClassLoader();
 | |
|         mResources = systemContext.getResources();
 | |
|         mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo);
 | |
|     }
 | |
| 
 | |
|     public String getPackageName() {
 | |
|         return mPackageName;
 | |
|     }
 | |
| 
 | |
|     public ApplicationInfo getApplicationInfo() {
 | |
|         return mApplicationInfo;
 | |
|     }
 | |
| 
 | |
|     public boolean isSecurityViolation() {
 | |
|         return mSecurityViolation;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the array of shared libraries that are listed as
 | |
|      * used by the given package.
 | |
|      *
 | |
|      * @param packageName the name of the package (note: not its
 | |
|      * file name)
 | |
|      * @return null-ok; the array of shared libraries, each one
 | |
|      * a fully-qualified path
 | |
|      */
 | |
|     private static String[] getLibrariesFor(String packageName) {
 | |
|         ApplicationInfo ai = null;
 | |
|         try {
 | |
|             ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
 | |
|                     PackageManager.GET_SHARED_LIBRARY_FILES);
 | |
|         } catch (RemoteException e) {
 | |
|             throw new AssertionError(e);
 | |
|         }
 | |
| 
 | |
|         if (ai == null) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         return ai.sharedLibraryFiles;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Combines two arrays (of library names) such that they are
 | |
|      * concatenated in order but are devoid of duplicates. The
 | |
|      * result is a single string with the names of the libraries
 | |
|      * separated by colons, or <code>null</code> if both lists
 | |
|      * were <code>null</code> or empty.
 | |
|      *
 | |
|      * @param list1 null-ok; the first list
 | |
|      * @param list2 null-ok; the second list
 | |
|      * @return null-ok; the combination
 | |
|      */
 | |
|     private static String combineLibs(String[] list1, String[] list2) {
 | |
|         StringBuilder result = new StringBuilder(300);
 | |
|         boolean first = true;
 | |
| 
 | |
|         if (list1 != null) {
 | |
|             for (String s : list1) {
 | |
|                 if (first) {
 | |
|                     first = false;
 | |
|                 } else {
 | |
|                     result.append(':');
 | |
|                 }
 | |
|                 result.append(s);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Only need to check for duplicates if list1 was non-empty.
 | |
|         boolean dupCheck = !first;
 | |
| 
 | |
|         if (list2 != null) {
 | |
|             for (String s : list2) {
 | |
|                 if (dupCheck && ArrayUtils.contains(list1, s)) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 if (first) {
 | |
|                     first = false;
 | |
|                 } else {
 | |
|                     result.append(':');
 | |
|                 }
 | |
|                 result.append(s);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return result.toString();
 | |
|     }
 | |
| 
 | |
|     public ClassLoader getClassLoader() {
 | |
|         synchronized (this) {
 | |
|             if (mClassLoader != null) {
 | |
|                 return mClassLoader;
 | |
|             }
 | |
| 
 | |
|             if (mIncludeCode && !mPackageName.equals("android")) {
 | |
|                 String zip = mAppDir;
 | |
| 
 | |
|                 /*
 | |
|                  * The following is a bit of a hack to inject
 | |
|                  * instrumentation into the system: If the app
 | |
|                  * being started matches one of the instrumentation names,
 | |
|                  * then we combine both the "instrumentation" and
 | |
|                  * "instrumented" app into the path, along with the
 | |
|                  * concatenation of both apps' shared library lists.
 | |
|                  */
 | |
| 
 | |
|                 String instrumentationAppDir =
 | |
|                         mActivityThread.mInstrumentationAppDir;
 | |
|                 String instrumentationAppPackage =
 | |
|                         mActivityThread.mInstrumentationAppPackage;
 | |
|                 String instrumentedAppDir =
 | |
|                         mActivityThread.mInstrumentedAppDir;
 | |
|                 String[] instrumentationLibs = null;
 | |
| 
 | |
|                 if (mAppDir.equals(instrumentationAppDir)
 | |
|                         || mAppDir.equals(instrumentedAppDir)) {
 | |
|                     zip = instrumentationAppDir + ":" + instrumentedAppDir;
 | |
|                     if (! instrumentedAppDir.equals(instrumentationAppDir)) {
 | |
|                         instrumentationLibs =
 | |
|                             getLibrariesFor(instrumentationAppPackage);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if ((mSharedLibraries != null) ||
 | |
|                         (instrumentationLibs != null)) {
 | |
|                     zip =
 | |
|                         combineLibs(mSharedLibraries, instrumentationLibs)
 | |
|                         + ':' + zip;
 | |
|                 }
 | |
| 
 | |
|                 /*
 | |
|                  * With all the combination done (if necessary, actually
 | |
|                  * create the class loader.
 | |
|                  */
 | |
| 
 | |
|                 if (ActivityThread.localLOGV)
 | |
|                     Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir);
 | |
| 
 | |
|                 mClassLoader =
 | |
|                     ApplicationLoaders.getDefault().getClassLoader(
 | |
|                         zip, mLibDir, mBaseClassLoader);
 | |
|                 initializeJavaContextClassLoader();
 | |
|             } else {
 | |
|                 if (mBaseClassLoader == null) {
 | |
|                     mClassLoader = ClassLoader.getSystemClassLoader();
 | |
|                 } else {
 | |
|                     mClassLoader = mBaseClassLoader;
 | |
|                 }
 | |
|             }
 | |
|             return mClassLoader;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Setup value for Thread.getContextClassLoader(). If the
 | |
|      * package will not run in in a VM with other packages, we set
 | |
|      * the Java context ClassLoader to the
 | |
|      * PackageInfo.getClassLoader value. However, if this VM can
 | |
|      * contain multiple packages, we intead set the Java context
 | |
|      * ClassLoader to a proxy that will warn about the use of Java
 | |
|      * context ClassLoaders and then fall through to use the
 | |
|      * system ClassLoader.
 | |
|      *
 | |
|      * <p> Note that this is similar to but not the same as the
 | |
|      * android.content.Context.getClassLoader(). While both
 | |
|      * context class loaders are typically set to the
 | |
|      * PathClassLoader used to load the package archive in the
 | |
|      * single application per VM case, a single Android process
 | |
|      * may contain several Contexts executing on one thread with
 | |
|      * their own logical ClassLoaders while the Java context
 | |
|      * ClassLoader is a thread local. This is why in the case when
 | |
|      * we have multiple packages per VM we do not set the Java
 | |
|      * context ClassLoader to an arbitrary but instead warn the
 | |
|      * user to set their own if we detect that they are using a
 | |
|      * Java library that expects it to be set.
 | |
|      */
 | |
|     private void initializeJavaContextClassLoader() {
 | |
|         IPackageManager pm = ActivityThread.getPackageManager();
 | |
|         android.content.pm.PackageInfo pi;
 | |
|         try {
 | |
|             pi = pm.getPackageInfo(mPackageName, 0);
 | |
|         } catch (RemoteException e) {
 | |
|             throw new AssertionError(e);
 | |
|         }
 | |
|         /*
 | |
|          * Two possible indications that this package could be
 | |
|          * sharing its virtual machine with other packages:
 | |
|          *
 | |
|          * 1.) the sharedUserId attribute is set in the manifest,
 | |
|          *     indicating a request to share a VM with other
 | |
|          *     packages with the same sharedUserId.
 | |
|          *
 | |
|          * 2.) the application element of the manifest has an
 | |
|          *     attribute specifying a non-default process name,
 | |
|          *     indicating the desire to run in another packages VM.
 | |
|          */
 | |
|         boolean sharedUserIdSet = (pi.sharedUserId != null);
 | |
|         boolean processNameNotDefault =
 | |
|             (pi.applicationInfo != null &&
 | |
|              !mPackageName.equals(pi.applicationInfo.processName));
 | |
|         boolean sharable = (sharedUserIdSet || processNameNotDefault);
 | |
|         ClassLoader contextClassLoader =
 | |
|             (sharable)
 | |
|             ? new WarningContextClassLoader()
 | |
|             : mClassLoader;
 | |
|         Thread.currentThread().setContextClassLoader(contextClassLoader);
 | |
|     }
 | |
| 
 | |
|     private static class WarningContextClassLoader extends ClassLoader {
 | |
| 
 | |
|         private static boolean warned = false;
 | |
| 
 | |
|         private void warn(String methodName) {
 | |
|             if (warned) {
 | |
|                 return;
 | |
|             }
 | |
|             warned = true;
 | |
|             Thread.currentThread().setContextClassLoader(getParent());
 | |
|             Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " +
 | |
|                   "The class loader returned by " +
 | |
|                   "Thread.getContextClassLoader() may fail for processes " +
 | |
|                   "that host multiple applications. You should explicitly " +
 | |
|                   "specify a context class loader. For example: " +
 | |
|                   "Thread.setContextClassLoader(getClass().getClassLoader());");
 | |
|         }
 | |
| 
 | |
|         @Override public URL getResource(String resName) {
 | |
|             warn("getResource");
 | |
|             return getParent().getResource(resName);
 | |
|         }
 | |
| 
 | |
|         @Override public Enumeration<URL> getResources(String resName) throws IOException {
 | |
|             warn("getResources");
 | |
|             return getParent().getResources(resName);
 | |
|         }
 | |
| 
 | |
|         @Override public InputStream getResourceAsStream(String resName) {
 | |
|             warn("getResourceAsStream");
 | |
|             return getParent().getResourceAsStream(resName);
 | |
|         }
 | |
| 
 | |
|         @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
 | |
|             warn("loadClass");
 | |
|             return getParent().loadClass(className);
 | |
|         }
 | |
| 
 | |
|         @Override public void setClassAssertionStatus(String cname, boolean enable) {
 | |
|             warn("setClassAssertionStatus");
 | |
|             getParent().setClassAssertionStatus(cname, enable);
 | |
|         }
 | |
| 
 | |
|         @Override public void setPackageAssertionStatus(String pname, boolean enable) {
 | |
|             warn("setPackageAssertionStatus");
 | |
|             getParent().setPackageAssertionStatus(pname, enable);
 | |
|         }
 | |
| 
 | |
|         @Override public void setDefaultAssertionStatus(boolean enable) {
 | |
|             warn("setDefaultAssertionStatus");
 | |
|             getParent().setDefaultAssertionStatus(enable);
 | |
|         }
 | |
| 
 | |
|         @Override public void clearAssertionStatus() {
 | |
|             warn("clearAssertionStatus");
 | |
|             getParent().clearAssertionStatus();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public String getAppDir() {
 | |
|         return mAppDir;
 | |
|     }
 | |
| 
 | |
|     public String getResDir() {
 | |
|         return mResDir;
 | |
|     }
 | |
| 
 | |
|     public String getDataDir() {
 | |
|         return mDataDir;
 | |
|     }
 | |
| 
 | |
|     public File getDataDirFile() {
 | |
|         return mDataDirFile;
 | |
|     }
 | |
| 
 | |
|     public AssetManager getAssets(ActivityThread mainThread) {
 | |
|         return getResources(mainThread).getAssets();
 | |
|     }
 | |
| 
 | |
|     public Resources getResources(ActivityThread mainThread) {
 | |
|         if (mResources == null) {
 | |
|             mResources = mainThread.getTopLevelResources(mResDir, this);
 | |
|         }
 | |
|         return mResources;
 | |
|     }
 | |
| 
 | |
|     public Application makeApplication(boolean forceDefaultAppClass,
 | |
|             Instrumentation instrumentation) {
 | |
|         if (mApplication != null) {
 | |
|             return mApplication;
 | |
|         }
 | |
| 
 | |
|         Application app = null;
 | |
| 
 | |
|         String appClass = mApplicationInfo.className;
 | |
|         if (forceDefaultAppClass || (appClass == null)) {
 | |
|             appClass = "android.app.Application";
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             java.lang.ClassLoader cl = getClassLoader();
 | |
|             ContextImpl appContext = new ContextImpl();
 | |
|             appContext.init(this, null, mActivityThread);
 | |
|             app = mActivityThread.mInstrumentation.newApplication(
 | |
|                     cl, appClass, appContext);
 | |
|             appContext.setOuterContext(app);
 | |
|         } catch (Exception e) {
 | |
|             if (!mActivityThread.mInstrumentation.onException(app, e)) {
 | |
|                 throw new RuntimeException(
 | |
|                     "Unable to instantiate application " + appClass
 | |
|                     + ": " + e.toString(), e);
 | |
|             }
 | |
|         }
 | |
|         mActivityThread.mAllApplications.add(app);
 | |
|         mApplication = app;
 | |
| 
 | |
|         if (instrumentation != null) {
 | |
|             try {
 | |
|                 instrumentation.callApplicationOnCreate(app);
 | |
|             } catch (Exception e) {
 | |
|                 if (!instrumentation.onException(app, e)) {
 | |
|                     throw new RuntimeException(
 | |
|                         "Unable to create application " + app.getClass().getName()
 | |
|                         + ": " + e.toString(), e);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return app;
 | |
|     }
 | |
| 
 | |
|     public void removeContextRegistrations(Context context,
 | |
|             String who, String what) {
 | |
|         HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
 | |
|             mReceivers.remove(context);
 | |
|         if (rmap != null) {
 | |
|             Iterator<LoadedApk.ReceiverDispatcher> it = rmap.values().iterator();
 | |
|             while (it.hasNext()) {
 | |
|                 LoadedApk.ReceiverDispatcher rd = it.next();
 | |
|                 IntentReceiverLeaked leak = new IntentReceiverLeaked(
 | |
|                         what + " " + who + " has leaked IntentReceiver "
 | |
|                         + rd.getIntentReceiver() + " that was " +
 | |
|                         "originally registered here. Are you missing a " +
 | |
|                         "call to unregisterReceiver()?");
 | |
|                 leak.setStackTrace(rd.getLocation().getStackTrace());
 | |
|                 Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
 | |
|                 try {
 | |
|                     ActivityManagerNative.getDefault().unregisterReceiver(
 | |
|                             rd.getIIntentReceiver());
 | |
|                 } catch (RemoteException e) {
 | |
|                     // system crashed, nothing we can do
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         mUnregisteredReceivers.remove(context);
 | |
|         //Slog.i(TAG, "Receiver registrations: " + mReceivers);
 | |
|         HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
 | |
|             mServices.remove(context);
 | |
|         if (smap != null) {
 | |
|             Iterator<LoadedApk.ServiceDispatcher> it = smap.values().iterator();
 | |
|             while (it.hasNext()) {
 | |
|                 LoadedApk.ServiceDispatcher sd = it.next();
 | |
|                 ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
 | |
|                         what + " " + who + " has leaked ServiceConnection "
 | |
|                         + sd.getServiceConnection() + " that was originally bound here");
 | |
|                 leak.setStackTrace(sd.getLocation().getStackTrace());
 | |
|                 Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
 | |
|                 try {
 | |
|                     ActivityManagerNative.getDefault().unbindService(
 | |
|                             sd.getIServiceConnection());
 | |
|                 } catch (RemoteException e) {
 | |
|                     // system crashed, nothing we can do
 | |
|                 }
 | |
|                 sd.doForget();
 | |
|             }
 | |
|         }
 | |
|         mUnboundServices.remove(context);
 | |
|         //Slog.i(TAG, "Service registrations: " + mServices);
 | |
|     }
 | |
| 
 | |
|     public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
 | |
|             Context context, Handler handler,
 | |
|             Instrumentation instrumentation, boolean registered) {
 | |
|         synchronized (mReceivers) {
 | |
|             LoadedApk.ReceiverDispatcher rd = null;
 | |
|             HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
 | |
|             if (registered) {
 | |
|                 map = mReceivers.get(context);
 | |
|                 if (map != null) {
 | |
|                     rd = map.get(r);
 | |
|                 }
 | |
|             }
 | |
|             if (rd == null) {
 | |
|                 rd = new ReceiverDispatcher(r, context, handler,
 | |
|                         instrumentation, registered);
 | |
|                 if (registered) {
 | |
|                     if (map == null) {
 | |
|                         map = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
 | |
|                         mReceivers.put(context, map);
 | |
|                     }
 | |
|                     map.put(r, rd);
 | |
|                 }
 | |
|             } else {
 | |
|                 rd.validate(context, handler);
 | |
|             }
 | |
|             return rd.getIIntentReceiver();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public IIntentReceiver forgetReceiverDispatcher(Context context,
 | |
|             BroadcastReceiver r) {
 | |
|         synchronized (mReceivers) {
 | |
|             HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context);
 | |
|             LoadedApk.ReceiverDispatcher rd = null;
 | |
|             if (map != null) {
 | |
|                 rd = map.get(r);
 | |
|                 if (rd != null) {
 | |
|                     map.remove(r);
 | |
|                     if (map.size() == 0) {
 | |
|                         mReceivers.remove(context);
 | |
|                     }
 | |
|                     if (r.getDebugUnregister()) {
 | |
|                         HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
 | |
|                                 = mUnregisteredReceivers.get(context);
 | |
|                         if (holder == null) {
 | |
|                             holder = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
 | |
|                             mUnregisteredReceivers.put(context, holder);
 | |
|                         }
 | |
|                         RuntimeException ex = new IllegalArgumentException(
 | |
|                                 "Originally unregistered here:");
 | |
|                         ex.fillInStackTrace();
 | |
|                         rd.setUnregisterLocation(ex);
 | |
|                         holder.put(r, rd);
 | |
|                     }
 | |
|                     return rd.getIIntentReceiver();
 | |
|                 }
 | |
|             }
 | |
|             HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
 | |
|                     = mUnregisteredReceivers.get(context);
 | |
|             if (holder != null) {
 | |
|                 rd = holder.get(r);
 | |
|                 if (rd != null) {
 | |
|                     RuntimeException ex = rd.getUnregisterLocation();
 | |
|                     throw new IllegalArgumentException(
 | |
|                             "Unregistering Receiver " + r
 | |
|                             + " that was already unregistered", ex);
 | |
|                 }
 | |
|             }
 | |
|             if (context == null) {
 | |
|                 throw new IllegalStateException("Unbinding Receiver " + r
 | |
|                         + " from Context that is no longer in use: " + context);
 | |
|             } else {
 | |
|                 throw new IllegalArgumentException("Receiver not registered: " + r);
 | |
|             }
 | |
| 
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static final class ReceiverDispatcher {
 | |
| 
 | |
|         final static class InnerReceiver extends IIntentReceiver.Stub {
 | |
|             final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
 | |
|             final LoadedApk.ReceiverDispatcher mStrongRef;
 | |
| 
 | |
|             InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
 | |
|                 mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
 | |
|                 mStrongRef = strong ? rd : null;
 | |
|             }
 | |
|             public void performReceive(Intent intent, int resultCode,
 | |
|                     String data, Bundle extras, boolean ordered, boolean sticky) {
 | |
|                 LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
 | |
|                 if (ActivityThread.DEBUG_BROADCAST) {
 | |
|                     int seq = intent.getIntExtra("seq", -1);
 | |
|                     Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
 | |
|                             + " to " + (rd != null ? rd.mReceiver : null));
 | |
|                 }
 | |
|                 if (rd != null) {
 | |
|                     rd.performReceive(intent, resultCode, data, extras,
 | |
|                             ordered, sticky);
 | |
|                 } else {
 | |
|                     // The activity manager dispatched a broadcast to a registered
 | |
|                     // receiver in this process, but before it could be delivered the
 | |
|                     // receiver was unregistered.  Acknowledge the broadcast on its
 | |
|                     // behalf so that the system's broadcast sequence can continue.
 | |
|                     if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
 | |
|                             "Finishing broadcast to unregistered receiver");
 | |
|                     IActivityManager mgr = ActivityManagerNative.getDefault();
 | |
|                     try {
 | |
|                         mgr.finishReceiver(this, resultCode, data, extras, false);
 | |
|                     } catch (RemoteException e) {
 | |
|                         Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         final IIntentReceiver.Stub mIIntentReceiver;
 | |
|         final BroadcastReceiver mReceiver;
 | |
|         final Context mContext;
 | |
|         final Handler mActivityThread;
 | |
|         final Instrumentation mInstrumentation;
 | |
|         final boolean mRegistered;
 | |
|         final IntentReceiverLeaked mLocation;
 | |
|         RuntimeException mUnregisterLocation;
 | |
| 
 | |
|         final class Args implements Runnable {
 | |
|             private Intent mCurIntent;
 | |
|             private int mCurCode;
 | |
|             private String mCurData;
 | |
|             private Bundle mCurMap;
 | |
|             private boolean mCurOrdered;
 | |
|             private boolean mCurSticky;
 | |
| 
 | |
|             public void run() {
 | |
|                 BroadcastReceiver receiver = mReceiver;
 | |
|                 if (ActivityThread.DEBUG_BROADCAST) {
 | |
|                     int seq = mCurIntent.getIntExtra("seq", -1);
 | |
|                     Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
 | |
|                             + " seq=" + seq + " to " + mReceiver);
 | |
|                     Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
 | |
|                             + " mCurOrdered=" + mCurOrdered);
 | |
|                 }
 | |
|                 
 | |
|                 IActivityManager mgr = ActivityManagerNative.getDefault();
 | |
|                 Intent intent = mCurIntent;
 | |
|                 mCurIntent = null;
 | |
|                 
 | |
|                 if (receiver == null) {
 | |
|                     if (mRegistered && mCurOrdered) {
 | |
|                         try {
 | |
|                             if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
 | |
|                                     "Finishing null broadcast to " + mReceiver);
 | |
|                             mgr.finishReceiver(mIIntentReceiver,
 | |
|                                     mCurCode, mCurData, mCurMap, false);
 | |
|                         } catch (RemoteException ex) {
 | |
|                         }
 | |
|                     }
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 try {
 | |
|                     ClassLoader cl =  mReceiver.getClass().getClassLoader();
 | |
|                     intent.setExtrasClassLoader(cl);
 | |
|                     if (mCurMap != null) {
 | |
|                         mCurMap.setClassLoader(cl);
 | |
|                     }
 | |
|                     receiver.setOrderedHint(true);
 | |
|                     receiver.setResult(mCurCode, mCurData, mCurMap);
 | |
|                     receiver.clearAbortBroadcast();
 | |
|                     receiver.setOrderedHint(mCurOrdered);
 | |
|                     receiver.setInitialStickyHint(mCurSticky);
 | |
|                     receiver.onReceive(mContext, intent);
 | |
|                 } catch (Exception e) {
 | |
|                     if (mRegistered && mCurOrdered) {
 | |
|                         try {
 | |
|                             if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
 | |
|                                     "Finishing failed broadcast to " + mReceiver);
 | |
|                             mgr.finishReceiver(mIIntentReceiver,
 | |
|                                     mCurCode, mCurData, mCurMap, false);
 | |
|                         } catch (RemoteException ex) {
 | |
|                         }
 | |
|                     }
 | |
|                     if (mInstrumentation == null ||
 | |
|                             !mInstrumentation.onException(mReceiver, e)) {
 | |
|                         throw new RuntimeException(
 | |
|                             "Error receiving broadcast " + intent
 | |
|                             + " in " + mReceiver, e);
 | |
|                     }
 | |
|                 }
 | |
|                 if (mRegistered && mCurOrdered) {
 | |
|                     try {
 | |
|                         if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
 | |
|                                 "Finishing broadcast to " + mReceiver);
 | |
|                         mgr.finishReceiver(mIIntentReceiver,
 | |
|                                 receiver.getResultCode(),
 | |
|                                 receiver.getResultData(),
 | |
|                                 receiver.getResultExtras(false),
 | |
|                                 receiver.getAbortBroadcast());
 | |
|                     } catch (RemoteException ex) {
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ReceiverDispatcher(BroadcastReceiver receiver, Context context,
 | |
|                 Handler activityThread, Instrumentation instrumentation,
 | |
|                 boolean registered) {
 | |
|             if (activityThread == null) {
 | |
|                 throw new NullPointerException("Handler must not be null");
 | |
|             }
 | |
| 
 | |
|             mIIntentReceiver = new InnerReceiver(this, !registered);
 | |
|             mReceiver = receiver;
 | |
|             mContext = context;
 | |
|             mActivityThread = activityThread;
 | |
|             mInstrumentation = instrumentation;
 | |
|             mRegistered = registered;
 | |
|             mLocation = new IntentReceiverLeaked(null);
 | |
|             mLocation.fillInStackTrace();
 | |
|         }
 | |
| 
 | |
|         void validate(Context context, Handler activityThread) {
 | |
|             if (mContext != context) {
 | |
|                 throw new IllegalStateException(
 | |
|                     "Receiver " + mReceiver +
 | |
|                     " registered with differing Context (was " +
 | |
|                     mContext + " now " + context + ")");
 | |
|             }
 | |
|             if (mActivityThread != activityThread) {
 | |
|                 throw new IllegalStateException(
 | |
|                     "Receiver " + mReceiver +
 | |
|                     " registered with differing handler (was " +
 | |
|                     mActivityThread + " now " + activityThread + ")");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         IntentReceiverLeaked getLocation() {
 | |
|             return mLocation;
 | |
|         }
 | |
| 
 | |
|         BroadcastReceiver getIntentReceiver() {
 | |
|             return mReceiver;
 | |
|         }
 | |
| 
 | |
|         IIntentReceiver getIIntentReceiver() {
 | |
|             return mIIntentReceiver;
 | |
|         }
 | |
| 
 | |
|         void setUnregisterLocation(RuntimeException ex) {
 | |
|             mUnregisterLocation = ex;
 | |
|         }
 | |
| 
 | |
|         RuntimeException getUnregisterLocation() {
 | |
|             return mUnregisterLocation;
 | |
|         }
 | |
| 
 | |
|         public void performReceive(Intent intent, int resultCode,
 | |
|                 String data, Bundle extras, boolean ordered, boolean sticky) {
 | |
|             if (ActivityThread.DEBUG_BROADCAST) {
 | |
|                 int seq = intent.getIntExtra("seq", -1);
 | |
|                 Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
 | |
|                         + " to " + mReceiver);
 | |
|             }
 | |
|             Args args = new Args();
 | |
|             args.mCurIntent = intent;
 | |
|             args.mCurCode = resultCode;
 | |
|             args.mCurData = data;
 | |
|             args.mCurMap = extras;
 | |
|             args.mCurOrdered = ordered;
 | |
|             args.mCurSticky = sticky;
 | |
|             if (!mActivityThread.post(args)) {
 | |
|                 if (mRegistered && ordered) {
 | |
|                     IActivityManager mgr = ActivityManagerNative.getDefault();
 | |
|                     try {
 | |
|                         if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
 | |
|                                 "Finishing sync broadcast to " + mReceiver);
 | |
|                         mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
 | |
|                                 args.mCurData, args.mCurMap, false);
 | |
|                     } catch (RemoteException ex) {
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public final IServiceConnection getServiceDispatcher(ServiceConnection c,
 | |
|             Context context, Handler handler, int flags) {
 | |
|         synchronized (mServices) {
 | |
|             LoadedApk.ServiceDispatcher sd = null;
 | |
|             HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
 | |
|             if (map != null) {
 | |
|                 sd = map.get(c);
 | |
|             }
 | |
|             if (sd == null) {
 | |
|                 sd = new ServiceDispatcher(c, context, handler, flags);
 | |
|                 if (map == null) {
 | |
|                     map = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
 | |
|                     mServices.put(context, map);
 | |
|                 }
 | |
|                 map.put(c, sd);
 | |
|             } else {
 | |
|                 sd.validate(context, handler);
 | |
|             }
 | |
|             return sd.getIServiceConnection();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public final IServiceConnection forgetServiceDispatcher(Context context,
 | |
|             ServiceConnection c) {
 | |
|         synchronized (mServices) {
 | |
|             HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map
 | |
|                     = mServices.get(context);
 | |
|             LoadedApk.ServiceDispatcher sd = null;
 | |
|             if (map != null) {
 | |
|                 sd = map.get(c);
 | |
|                 if (sd != null) {
 | |
|                     map.remove(c);
 | |
|                     sd.doForget();
 | |
|                     if (map.size() == 0) {
 | |
|                         mServices.remove(context);
 | |
|                     }
 | |
|                     if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
 | |
|                         HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
 | |
|                                 = mUnboundServices.get(context);
 | |
|                         if (holder == null) {
 | |
|                             holder = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
 | |
|                             mUnboundServices.put(context, holder);
 | |
|                         }
 | |
|                         RuntimeException ex = new IllegalArgumentException(
 | |
|                                 "Originally unbound here:");
 | |
|                         ex.fillInStackTrace();
 | |
|                         sd.setUnbindLocation(ex);
 | |
|                         holder.put(c, sd);
 | |
|                     }
 | |
|                     return sd.getIServiceConnection();
 | |
|                 }
 | |
|             }
 | |
|             HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
 | |
|                     = mUnboundServices.get(context);
 | |
|             if (holder != null) {
 | |
|                 sd = holder.get(c);
 | |
|                 if (sd != null) {
 | |
|                     RuntimeException ex = sd.getUnbindLocation();
 | |
|                     throw new IllegalArgumentException(
 | |
|                             "Unbinding Service " + c
 | |
|                             + " that was already unbound", ex);
 | |
|                 }
 | |
|             }
 | |
|             if (context == null) {
 | |
|                 throw new IllegalStateException("Unbinding Service " + c
 | |
|                         + " from Context that is no longer in use: " + context);
 | |
|             } else {
 | |
|                 throw new IllegalArgumentException("Service not registered: " + c);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static final class ServiceDispatcher {
 | |
|         private final ServiceDispatcher.InnerConnection mIServiceConnection;
 | |
|         private final ServiceConnection mConnection;
 | |
|         private final Context mContext;
 | |
|         private final Handler mActivityThread;
 | |
|         private final ServiceConnectionLeaked mLocation;
 | |
|         private final int mFlags;
 | |
| 
 | |
|         private RuntimeException mUnbindLocation;
 | |
| 
 | |
|         private boolean mDied;
 | |
| 
 | |
|         private static class ConnectionInfo {
 | |
|             IBinder binder;
 | |
|             IBinder.DeathRecipient deathMonitor;
 | |
|         }
 | |
| 
 | |
|         private static class InnerConnection extends IServiceConnection.Stub {
 | |
|             final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
 | |
| 
 | |
|             InnerConnection(LoadedApk.ServiceDispatcher sd) {
 | |
|                 mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
 | |
|             }
 | |
| 
 | |
|             public void connected(ComponentName name, IBinder service) throws RemoteException {
 | |
|                 LoadedApk.ServiceDispatcher sd = mDispatcher.get();
 | |
|                 if (sd != null) {
 | |
|                     sd.connected(name, service);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private final HashMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
 | |
|             = new HashMap<ComponentName, ServiceDispatcher.ConnectionInfo>();
 | |
| 
 | |
|         ServiceDispatcher(ServiceConnection conn,
 | |
|                 Context context, Handler activityThread, int flags) {
 | |
|             mIServiceConnection = new InnerConnection(this);
 | |
|             mConnection = conn;
 | |
|             mContext = context;
 | |
|             mActivityThread = activityThread;
 | |
|             mLocation = new ServiceConnectionLeaked(null);
 | |
|             mLocation.fillInStackTrace();
 | |
|             mFlags = flags;
 | |
|         }
 | |
| 
 | |
|         void validate(Context context, Handler activityThread) {
 | |
|             if (mContext != context) {
 | |
|                 throw new RuntimeException(
 | |
|                     "ServiceConnection " + mConnection +
 | |
|                     " registered with differing Context (was " +
 | |
|                     mContext + " now " + context + ")");
 | |
|             }
 | |
|             if (mActivityThread != activityThread) {
 | |
|                 throw new RuntimeException(
 | |
|                     "ServiceConnection " + mConnection +
 | |
|                     " registered with differing handler (was " +
 | |
|                     mActivityThread + " now " + activityThread + ")");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void doForget() {
 | |
|             synchronized(this) {
 | |
|                 Iterator<ServiceDispatcher.ConnectionInfo> it = mActiveConnections.values().iterator();
 | |
|                 while (it.hasNext()) {
 | |
|                     ServiceDispatcher.ConnectionInfo ci = it.next();
 | |
|                     ci.binder.unlinkToDeath(ci.deathMonitor, 0);
 | |
|                 }
 | |
|                 mActiveConnections.clear();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ServiceConnectionLeaked getLocation() {
 | |
|             return mLocation;
 | |
|         }
 | |
| 
 | |
|         ServiceConnection getServiceConnection() {
 | |
|             return mConnection;
 | |
|         }
 | |
| 
 | |
|         IServiceConnection getIServiceConnection() {
 | |
|             return mIServiceConnection;
 | |
|         }
 | |
| 
 | |
|         int getFlags() {
 | |
|             return mFlags;
 | |
|         }
 | |
| 
 | |
|         void setUnbindLocation(RuntimeException ex) {
 | |
|             mUnbindLocation = ex;
 | |
|         }
 | |
| 
 | |
|         RuntimeException getUnbindLocation() {
 | |
|             return mUnbindLocation;
 | |
|         }
 | |
| 
 | |
|         public void connected(ComponentName name, IBinder service) {
 | |
|             if (mActivityThread != null) {
 | |
|                 mActivityThread.post(new RunConnection(name, service, 0));
 | |
|             } else {
 | |
|                 doConnected(name, service);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void death(ComponentName name, IBinder service) {
 | |
|             ServiceDispatcher.ConnectionInfo old;
 | |
| 
 | |
|             synchronized (this) {
 | |
|                 mDied = true;
 | |
|                 old = mActiveConnections.remove(name);
 | |
|                 if (old == null || old.binder != service) {
 | |
|                     // Death for someone different than who we last
 | |
|                     // reported...  just ignore it.
 | |
|                     return;
 | |
|                 }
 | |
|                 old.binder.unlinkToDeath(old.deathMonitor, 0);
 | |
|             }
 | |
| 
 | |
|             if (mActivityThread != null) {
 | |
|                 mActivityThread.post(new RunConnection(name, service, 1));
 | |
|             } else {
 | |
|                 doDeath(name, service);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void doConnected(ComponentName name, IBinder service) {
 | |
|             ServiceDispatcher.ConnectionInfo old;
 | |
|             ServiceDispatcher.ConnectionInfo info;
 | |
| 
 | |
|             synchronized (this) {
 | |
|                 old = mActiveConnections.get(name);
 | |
|                 if (old != null && old.binder == service) {
 | |
|                     // Huh, already have this one.  Oh well!
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (service != null) {
 | |
|                     // A new service is being connected... set it all up.
 | |
|                     mDied = false;
 | |
|                     info = new ConnectionInfo();
 | |
|                     info.binder = service;
 | |
|                     info.deathMonitor = new DeathMonitor(name, service);
 | |
|                     try {
 | |
|                         service.linkToDeath(info.deathMonitor, 0);
 | |
|                         mActiveConnections.put(name, info);
 | |
|                     } catch (RemoteException e) {
 | |
|                         // This service was dead before we got it...  just
 | |
|                         // don't do anything with it.
 | |
|                         mActiveConnections.remove(name);
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                 } else {
 | |
|                     // The named service is being disconnected... clean up.
 | |
|                     mActiveConnections.remove(name);
 | |
|                 }
 | |
| 
 | |
|                 if (old != null) {
 | |
|                     old.binder.unlinkToDeath(old.deathMonitor, 0);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If there was an old service, it is not disconnected.
 | |
|             if (old != null) {
 | |
|                 mConnection.onServiceDisconnected(name);
 | |
|             }
 | |
|             // If there is a new service, it is now connected.
 | |
|             if (service != null) {
 | |
|                 mConnection.onServiceConnected(name, service);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void doDeath(ComponentName name, IBinder service) {
 | |
|             mConnection.onServiceDisconnected(name);
 | |
|         }
 | |
| 
 | |
|         private final class RunConnection implements Runnable {
 | |
|             RunConnection(ComponentName name, IBinder service, int command) {
 | |
|                 mName = name;
 | |
|                 mService = service;
 | |
|                 mCommand = command;
 | |
|             }
 | |
| 
 | |
|             public void run() {
 | |
|                 if (mCommand == 0) {
 | |
|                     doConnected(mName, mService);
 | |
|                 } else if (mCommand == 1) {
 | |
|                     doDeath(mName, mService);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             final ComponentName mName;
 | |
|             final IBinder mService;
 | |
|             final int mCommand;
 | |
|         }
 | |
| 
 | |
|         private final class DeathMonitor implements IBinder.DeathRecipient
 | |
|         {
 | |
|             DeathMonitor(ComponentName name, IBinder service) {
 | |
|                 mName = name;
 | |
|                 mService = service;
 | |
|             }
 | |
| 
 | |
|             public void binderDied() {
 | |
|                 death(mName, mService);
 | |
|             }
 | |
| 
 | |
|             final ComponentName mName;
 | |
|             final IBinder mService;
 | |
|         }
 | |
|     }
 | |
| }
 | 
