| /* |
| * 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.os.StrictMode; |
| 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); |
| |
| // Temporarily disable logging of disk reads on the Looper thread |
| // as this is early and necessary. |
| StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| |
| mClassLoader = |
| ApplicationLoaders.getDefault().getClassLoader( |
| zip, mLibDir, mBaseClassLoader); |
| initializeJavaContextClassLoader(); |
| |
| StrictMode.setThreadPolicy(oldPolicy); |
| } 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 extends BroadcastReceiver.PendingResult implements Runnable { |
| private Intent mCurIntent; |
| private final boolean mOrdered; |
| |
| public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras, |
| boolean ordered, boolean sticky) { |
| super(resultCode, resultData, resultExtras, |
| mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, |
| ordered, sticky, mIIntentReceiver.asBinder()); |
| mCurIntent = intent; |
| mOrdered = ordered; |
| } |
| |
| public void run() { |
| final BroadcastReceiver receiver = mReceiver; |
| final boolean ordered = mOrdered; |
| |
| 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 |
| + " mOrderedHint=" + ordered); |
| } |
| |
| final IActivityManager mgr = ActivityManagerNative.getDefault(); |
| final Intent intent = mCurIntent; |
| mCurIntent = null; |
| |
| if (receiver == null) { |
| if (mRegistered && ordered) { |
| if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, |
| "Finishing null broadcast to " + mReceiver); |
| sendFinished(mgr); |
| } |
| return; |
| } |
| |
| try { |
| ClassLoader cl = mReceiver.getClass().getClassLoader(); |
| intent.setExtrasClassLoader(cl); |
| setExtrasClassLoader(cl); |
| receiver.setPendingResult(this); |
| receiver.onReceive(mContext, intent); |
| } catch (Exception e) { |
| if (mRegistered && ordered) { |
| if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, |
| "Finishing failed broadcast to " + mReceiver); |
| sendFinished(mgr); |
| } |
| if (mInstrumentation == null || |
| !mInstrumentation.onException(mReceiver, e)) { |
| throw new RuntimeException( |
| "Error receiving broadcast " + intent |
| + " in " + mReceiver, e); |
| } |
| } |
| |
| if (receiver.getPendingResult() != null) { |
| finish(); |
| } |
| } |
| } |
| |
| 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(intent, resultCode, data, extras, ordered, sticky); |
| if (!mActivityThread.post(args)) { |
| if (mRegistered && ordered) { |
| IActivityManager mgr = ActivityManagerNative.getDefault(); |
| if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, |
| "Finishing sync broadcast to " + mReceiver); |
| args.sendFinished(mgr); |
| } |
| } |
| } |
| |
| } |
| |
| 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; |
| } |
| } |
| } |