Android Open Source - android-autostarts Receiver Reader






From Project

Back to project page android-autostarts.

License

The source code is released under:

GNU General Public License

If you think the Android project android-autostarts listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.elsdoerfer.android.autostarts;
//from   w ww.j ava  2  s  .  c  o m
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import com.elsdoerfer.android.autostarts.db.ComponentInfo;
import com.elsdoerfer.android.autostarts.db.IntentFilterInfo;
import com.elsdoerfer.android.autostarts.db.PackageInfo;

import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.util.Log;


/**
 * Load the broadcast receivers installed by applications.
 *
 * Android provides some introspection capabilities through it's
 * PackageManager API, but this is insufficient:
 *
 * 1) Looping through the list of installed packages and collecting
 *    all receivers doesn't expose to us the intents a receiver has
 *    registered for. See the unimplemented
 *    PackageManager.GET_INTENT_FILTERS, and the following URLs:
 *       http://groups.google.com/group/android-developers/browse_thread/thread/4502827143ea9b20
 *       http://groups.google.com/group/android-developers/browse_thread/thread/ef0e4b390552f2c/
 *
 * 2) Using PackageManager.queryBroadcastReceivers() to find all installed
 *    receivers works, but has numerous restrictions:
 *        * We need an internal list of actions that we support and
 *          query for.
 *        * Disabled components are never returned.
 *        * Receivers who's intent filters match certain data are only
 *          returned when our query matches the receiver's filters.
 *    It is possible to work around those issues, for example by
 *    remembering what components have been disabled by the user, and
 *    we used to do this in the past, but in the end it's a very
 *    restricting approach.
 *
 * Fortunately, it's relatively simple to parse the AndroidManifest.xml
 * files of every package ourselves and extract the data we need.
 *
 * Parts of this were adapted from ManifestExplorer:
 *     https://www.isecpartners.com/manifest_explorer.html
 */
public class ReceiverReader {

  private static final boolean LOGV = ListActivity.LOGV;
  private static final String TAG = Utils.TAG;

  // From com.android.sdklib.SdkConstants.NS_RESOURCES.
  private final static String SDK_NS_RESOURCES =
    "http://schemas.android.com/apk/res/android";

  public interface OnLoadProgressListener {
    public void onProgress(ArrayList<IntentFilterInfo> currentState, float progress);
  }

  private static enum ParserState { Unknown, InManifest,
    InApplication, InReceiver, InIntentFilter, InAction }

  private final Context mContext;
  private final PackageManager mPackageManager;
  private XmlResourceParser mCurrentXML;
  private Resources mCurrentResources;
  private OnLoadProgressListener mOnLoadProgressListener;
  private ArrayList<IntentFilterInfo> mResult;

  // Parser state flags
  android.content.pm.PackageInfo mAndroidPackage = null;
  String mCurrentApplicationLabel = null;
  PackageInfo mCurrentPackage = null;
  ParserState mCurrentState = ParserState.Unknown;
  ComponentInfo mCurrentComponent = null;
  int mCurrentFilterPriority = 0;

  /**
   * Constructor.
   */
  public ReceiverReader(Context context,
      OnLoadProgressListener progressListener) {
    mContext = context;
    mPackageManager = mContext.getPackageManager();
    mOnLoadProgressListener = progressListener;
  }

  /**
   * Main method to make this class go ahead and do it's job.
   */
  public ArrayList<IntentFilterInfo> load() {
    mResult = new ArrayList<IntentFilterInfo>();

    List<android.content.pm.PackageInfo> packages =
      mPackageManager.getInstalledPackages(PackageManager.GET_DISABLED_COMPONENTS);
    int packageCount = packages.size();
    for (int i=0; i<packageCount; i++)
    {
      android.content.pm.PackageInfo p = packages.get(i);

      if (LOGV) Log.v(TAG, "Processing package "+p.packageName);
      parsePackage(p);

      // Publish an update after every package
      if (mOnLoadProgressListener != null) {
        // It's important that we send out a copy there, or we'll
        // can run into crashes both inside our ListAdapter filtering
        // code, where we are iterating over a list that can be
        // changed by the thread in the background simultaneously
        // (ConcurrentModificationException), and the core Android
        // ListAdapter stuff itself which complains about the
        // data having changed without notifyDataSetChanged being
        // called. Note that this is a shallow copy, the actual
        // objects in both lists remain the same. This is important
        // so that when a receiver status is toggled while we are
        // still loading, it's changed attributes are not reset by
        // the next progress update.
        @SuppressWarnings("unchecked")
        ArrayList<IntentFilterInfo> copy = (ArrayList<IntentFilterInfo>) mResult.clone();
        mOnLoadProgressListener.onProgress(copy, i/(float)packageCount);
      }
    }

    return mResult;
  }

  private void parsePackage(android.content.pm.PackageInfo p) {
    // Open the manifest file
    XmlResourceParser xml = null;
    Resources resources = null;
    try {
      Context scannedAppContext = mContext.createPackageContext(p.packageName, 0);
      AssetManager assets = scannedAppContext.getAssets();
      xml = openManifest(scannedAppContext, assets);
      resources = new Resources(assets, mContext.getResources().getDisplayMetrics(), null);
    } catch (IOException e) {
      Log.e(TAG, "Unable to open manifest or resources for "+p.packageName, e);
    } catch (NameNotFoundException e) {
      Log.e(TAG, "Unable to open manifest or resources for "+p.packageName, e);
    } catch (NullPointerException e) {
      // I've been seeing a lot of NullPointerException's in
      // "android.app.ApplicationContext.init", called by
      // createPackageContext(). Due to the help of a user it was
      // determined that the problem can be reproduced by removing
      // or renaming an application file in /data/app. That this is
      // going to cause unexpected problems - given. But why is
      // it happening in the first place on production phones?
      //
      // Anyway, as I said, this is happening a lot - a bunch of
      // stacktraces get sent my way every single day. This should
      // be working around it.
      Log.e(TAG, "Error processing "+p.packageName + " - most likely,"+
          " the createPackageContext() call failed; if so, your"+
          " application directory is somehow screwed up. Android" +
          " is probably to blame, since it shouldn't be happening. Skipping.", e);
    }

    if (xml == null)
      return;

    mAndroidPackage = p;
    mCurrentPackage = null;
    mCurrentXML = xml;
    mCurrentResources = resources;

    try {
      String tagName = null;
      mCurrentState = ParserState.Unknown;
      int eventType = xml.getEventType();
      while (eventType != XmlPullParser.END_DOCUMENT) {
        switch (eventType) {
        case XmlPullParser.START_TAG:
          tagName = xml.getName();
          if (tagName.equals("manifest"))
            startManifest();
          else if (tagName.equals("application"))
            startApplication();
          else if (tagName.equals("receiver"))
            startReceiver();
          else if (tagName.equals("intent-filter"))
            startIntentFilter();
          else if (tagName.equals("action"))
            startAction();
          break;

        case XmlPullParser.END_TAG:
          tagName = xml.getName();
          if (tagName.equals("manifest"))
            endManifest();
          else if (tagName.equals("application"))
            endApplication();
          else if (tagName.equals("receiver"))
            endReceiver();
          else if (tagName.equals("intent-filter"))
            endIntentFilter();
          else if (tagName.equals("action"))
            endAction();
          break;
        }
        eventType = xml.nextToken();
      }
    } catch (XmlPullParserException e) {
      Log.e(TAG, "Unable to process manifest for "+p.packageName, e);
    } catch (IOException e) {
      Log.e(TAG, "Unable to process manifest for "+p.packageName, e);
    }
    finally {
      mCurrentXML = null;
      mCurrentResources = null;
    }
  }

  /**
   * Open AndroidManifest.xml file stored in apk,
   * working around skin manifest bug on some Xperia devices
   */
  private XmlResourceParser openManifest(Context scannedAppContext, AssetManager assets) throws IOException {
    try {
      // Use reflection to avoid VerifyError on old devices, this is equivalent to:
      //String packageResourcePath = scannedAppContext.getPackageResourcePath();
      String packageResourcePath = (String) Context.class.getMethod("getPackageResourcePath")
          .invoke(scannedAppContext);

      // getCookieName is @hide method, it returns name of apk file for given asset cookie
      Method getCookieName = AssetManager.class.getMethod("getCookieName", int.class);

      // "android" package has no resource path, use hardcoded path,
      // If we won't find it, we'll open manifest in non-workaround way
      if (packageResourcePath == null && scannedAppContext.getPackageName().equals("android")) {
        packageResourcePath = "/system/framework/framework-res.apk";
      }

      // This loop shouldn't reach 20,
      // getCookieName will throw IndexOutOfBoundsException when passed illegal cookie,
      // but we expect to find right apk and return earlier.
      for (int i = 1; i < 20; i++) {
        if (packageResourcePath.equals(getCookieName.invoke(assets, i))) {
          return assets.openXmlResourceParser(i, "AndroidManifest.xml");
        }
      }
    } catch (Exception ignored) {
      // Something went wrong with workaround, ignore and use normal method
    }

    // Normal way of opening manifest, used if workaround above fails
    return assets.openXmlResourceParser("AndroidManifest.xml");
  }

  void startManifest() {
    if (mCurrentState == ParserState.Unknown)
      mCurrentState = ParserState.InManifest;
  }

  void endManifest() {
    if (mCurrentState == ParserState.InManifest)
      mCurrentState = ParserState.Unknown;
  }

  void startApplication() {
    if (mCurrentState != ParserState.InManifest)
      return;
    mCurrentState = ParserState.InApplication;
    mCurrentApplicationLabel = getAttr("label");
  }

  void endApplication() {
    if (mCurrentState == ParserState.InApplication) {
      mCurrentState = ParserState.InManifest;
      mCurrentApplicationLabel = null;
    }
  }

  void startReceiver() {
    if (mCurrentState != ParserState.InApplication)
      return;

    mCurrentState = ParserState.InReceiver;

    // Build the component name. We need to do some normalization here,
    // since we can get the original string the dev. put into his XML.
    // Our current logic is: If the component name starts with a dot,
    // or doesn't contain one, we assume a relative name and prepend the
    // package name. Otherwise, we consider the component name to be
    // absolute already.
    String componentName = getAttr("name");
    if (componentName == null) {
      Log.e(TAG, "A receiver in "+mAndroidPackage.packageName+" has no name.");
      componentName = "(no-name)";
    }
    else if (componentName.startsWith("."))
      componentName = mAndroidPackage.packageName + componentName;
    else if (!componentName.contains("."))
      componentName = mAndroidPackage.packageName + "." + componentName;

    // Note that we specifically delay creating the package object
    // until we are sure there are actually receivers in this package.
    if (mCurrentPackage == null) {
      mCurrentPackage = new PackageInfo(mAndroidPackage);
      mCurrentPackage.isSystem = isSystemApp(mAndroidPackage);
      mCurrentPackage.packageLabel = mCurrentApplicationLabel;
      // TODO: Traceview says this takes 9% of the total load
      // time. We could move it to the drawing code (load only
      // once the user actually sees an icon), but that would
      // slow down the list view usage. One option possibly would
      // be to load it on-demand, but do that again in a thread.
      mCurrentPackage.icon =
        mAndroidPackage.applicationInfo.loadIcon(mPackageManager);
    }

    mCurrentComponent = new ComponentInfo();
    mCurrentComponent.packageInfo = mCurrentPackage;
    mCurrentComponent.componentName = componentName;
    mCurrentComponent.componentLabel = getAttr("label");
    mCurrentComponent.defaultEnabled = !(getAttr("enabled") == "false");;
    mCurrentComponent.currentEnabledState =
        mPackageManager.getComponentEnabledSetting(
          new ComponentName(mCurrentPackage.packageName,
              mCurrentComponent.componentName));
  }

  void endReceiver() {
    if (mCurrentState == ParserState.InReceiver) {
      mCurrentComponent = null;
      mCurrentState = ParserState.InApplication;
    }
  }

  void startIntentFilter() {
    if (mCurrentState != ParserState.InReceiver)
       return;

    mCurrentState = ParserState.InIntentFilter;

    String priorityRaw = getAttr("priority");
    if (priorityRaw != null)
      try {
        mCurrentFilterPriority = Integer.parseInt(priorityRaw);
      } catch (NumberFormatException e) {
        Log.w(TAG, "Unable to parse priority value "+
            "for receiver "+mCurrentComponent.componentName+
            " in package "+mCurrentPackage.packageName+": "+priorityRaw);
      }
      if (LOGV && mCurrentFilterPriority != 0)
        Log.v(TAG, "Receiver "+mCurrentComponent.componentName+
            " in package "+mCurrentPackage.packageName+" has "+
        "an intent filter with priority != 0");
    }

  void endIntentFilter() {
    if (mCurrentState == ParserState.InIntentFilter) {
      mCurrentState = ParserState.InReceiver;
      mCurrentFilterPriority = 0;
    }
  }

  void startAction() {
    if (mCurrentState != ParserState.InIntentFilter)
      return;

    mCurrentState = ParserState.InAction;

    // A component name is missing, we can't proceed.
    if (mCurrentComponent == null)
      return;

    String action = getAttr("name");
    if (action == null) {
      Log.w(TAG, "Receiver "+mCurrentComponent.componentName+
             " of package "+mCurrentPackage.packageName+" has "+
                 "action without name");
      return;
    }

    // Add this receiver to the result
    IntentFilterInfo filter = new IntentFilterInfo(
        mCurrentComponent, action, mCurrentFilterPriority);
    mResult.add(filter);
  }

  void endAction() {
    if (mCurrentState == ParserState.InAction)
      mCurrentState = ParserState.InIntentFilter;
  }

  /**
   * True if this app is installed on the system partition.
   */
  static boolean isSystemApp(android.content.pm.PackageInfo p) {
    // You'd think that it would be possible to determine the
    // system status of packages that do not have a application,
    // as rare as that may be, but alas, it doesn't look like it.
    // Of course, in those cases there'd be no receivers for us
    // either, so we don't really care about this case.
    return ((p.applicationInfo != null)  && (
        ApplicationInfo.FLAG_SYSTEM & p.applicationInfo.flags)
        == ApplicationInfo.FLAG_SYSTEM);
  }

  /**
   * Returns the requested attribute value, or null.
   *
   * Ensures that we only read from the Android namespace, and resolves
   * resource identifiers if necessary.
   */
  private String getAttr(String attributeName) {
    String value = mCurrentXML.getAttributeValue(SDK_NS_RESOURCES, attributeName);

    // In some, rarer cases (example: com.mxtech.videoplayer.MediaButtonReceiver), the
    // xml attributes seem to be resource encoded such that it is not possibly to query
    // by name. In fact, getAttributeName(0) returns an empty string. In such cases, the
    // attribute rather tha a name seems to be a resource id (getAttributeNameResource),
    // pointing to a resource in the framework.
    // We have to look at all such attributes, resolve the resource to a name, ad then
    // we can check *that*.
    // See:
    //   https://code.google.com/p/android-apktool/issues/detail?id=512
    //   https://github.com/iBotPeaches/Apktool/commit/e126a51b4bb8991042b48ec5bf916f396e75f6f0#diff-0e7a43a489bb79777d002aed6191d459R317
    if (value == null) {
      for (int i=0; i<mCurrentXML.getAttributeCount(); i++) {
        if (!mCurrentXML.getAttributeName(i).equals(""))
          // Normally, both the name and resource return values. Unless the
          // attribute name is empty we don't need this code and we can trust
          // the check above to find the attribute.
          continue;

        int res = mCurrentXML.getAttributeNameResource(i);
        if (res != 0) {
          String sName = mCurrentResources.getResourceEntryName(res);
          if (sName.equals(attributeName)) {
            value = mCurrentXML.getAttributeValue(i);
            break;
          }
        }
      }
    }

    // TODO: It's possible to use getAttributeResourceValue and check for
    // default value return rather than parsing the @ ourselves. Is it faster?
    return resolveValue(value, mCurrentResources);
  }

  /**
   * Return the value, resolving it through the provided resources if
   * it appears to be a resource ID. Otherwise just returns what was
   * provided.
   */
  private String resolveValue(String in, Resources r) {
    if (in == null || !in.startsWith("@") || r == null)
      return in;
    try {
      int num = Integer.parseInt(in.substring(1));
      return r.getString(num);
    } catch (NumberFormatException e) {
      return in;
    } catch (NotFoundException e) {
      Log.w(TAG, "Unable to resolve resource "+in, e);
      return in;
    }
    // ManifestExplorer used this catch-all, not sure why. Seems to
    // work fine without it, for now. Note that we added the
    // NotFoundException catch ourselves.
    //catch (RuntimeException e) {
    // formerly noted errors here, but simply not resolving works better
    //  return in;
    //}
  }
}




Java Source Code List

com.elsdoerfer.android.autostarts.Actions.java
com.elsdoerfer.android.autostarts.DatabaseHelper.java
com.elsdoerfer.android.autostarts.EventDetailsFragment.java
com.elsdoerfer.android.autostarts.HelpActivity.java
com.elsdoerfer.android.autostarts.ListActivity.java
com.elsdoerfer.android.autostarts.LoadTask.java
com.elsdoerfer.android.autostarts.MyExpandableListAdapter.java
com.elsdoerfer.android.autostarts.ReceiverReader.java
com.elsdoerfer.android.autostarts.ToggleService.java
com.elsdoerfer.android.autostarts.ToggleTool.java
com.elsdoerfer.android.autostarts.Utils.java
com.elsdoerfer.android.autostarts.compat.FixedExpandableListView.java
com.elsdoerfer.android.autostarts.db.ComponentInfo.java
com.elsdoerfer.android.autostarts.db.IntentFilterInfo.java
com.elsdoerfer.android.autostarts.db.PackageInfo.java
com.stericson.RootTools.Constants.java
com.stericson.RootTools.RootTools.java
com.stericson.RootTools.containers.Mount.java
com.stericson.RootTools.containers.Permissions.java
com.stericson.RootTools.containers.RootClass.java
com.stericson.RootTools.containers.Symlink.java
com.stericson.RootTools.exceptions.RootDeniedException.java
com.stericson.RootTools.execution.CommandCapture.java
com.stericson.RootTools.execution.Command.java
com.stericson.RootTools.execution.JavaCommandCapture.java
com.stericson.RootTools.execution.Shell.java
com.stericson.RootTools.internal.Installer.java
com.stericson.RootTools.internal.InternalVariables.java
com.stericson.RootTools.internal.Remounter.java
com.stericson.RootTools.internal.RootToolsInternalMethods.java
com.stericson.RootTools.internal.Runner.java
src.com.elsdoerfer.android.autostarts.opt.MarketUtils.java
src.com.elsdoerfer.android.autostarts.opt.MarketUtils.java
src.com.elsdoerfer.android.autostarts.opt.RootFeatures.java
src.com.elsdoerfer.android.autostarts.opt.RootFeatures.java