public class ActivityThread {

  public static IPackageManager getPackageManager() {
    return null;
  }

  public static ActivityThread systemMain() {
    return null;
  }

  public static ActivityThread currentActivityThread() {
    return null;
  }


  public static Application currentApplication() {
   return null;
  }

  public static String currentProcessName() {
    return null;
  }

  public ContextImpl getSystemContext() {
    return null;
  }

  public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {

  }



} public class AppOpsManager {

  public static final int MODE_ALLOWED = 0;


  public static final int MODE_IGNORED = 1;


  public static final int MODE_ERRORED = 2;


  public static final int MODE_DEFAULT = 3;


  public static int strOpToOp(String op) {
    return 0;
  }

  public static String permissionToOp(String s) {
    return null;
  }

  public static int permissionToOpCode(String s) {
    return 0;
  }

  public static int strDebugOpToOp(String op) {
    throw new IllegalArgumentException("Unknown operation string: " + op);
  }

  public static class PackageOps implements Parcelable {

    private final String mPackageName;
    private final int mUid;
    private final List<OpEntry> mEntries;

    public PackageOps(String packageName, int uid, List<OpEntry> entries) {
      mPackageName = packageName;
      mUid = uid;
      mEntries = entries;
    }

    public String getPackageName() {
      return mPackageName;
    }

    public int getUid() {
      return mUid;
    }

    public List<OpEntry> getOps() {
      return mEntries;
    }

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      dest.writeString(mPackageName);
      dest.writeInt(mUid);
      dest.writeInt(mEntries.size());
      for (int i = 0; i < mEntries.size(); i++) {
        mEntries.get(i).writeToParcel(dest, flags);
      }
    }

    PackageOps(Parcel source) {
      mPackageName = source.readString();
      mUid = source.readInt();
      mEntries = new ArrayList<OpEntry>();
      final int N = source.readInt();
      for (int i = 0; i < N; i++) {
        mEntries.add(OpEntry.CREATOR.createFromParcel(source));
      }
    }

    public static final Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
      @Override
      public PackageOps createFromParcel(Parcel source) {
        return new PackageOps(source);
      }

      @Override
      public PackageOps[] newArray(int size) {
        return new PackageOps[size];
      }
    };
  }


  public static class OpEntry implements Parcelable {

    private final int mOp;
    private final int mMode;
    private final long mTime;
    private final long mRejectTime;
    private final int mDuration;
    private final int mProxyUid;
    private final String mProxyPackageName;

    public OpEntry(int op, int mode, long time, long rejectTime, int duration,
        int proxyUid, String proxyPackage) {
      mOp = op;
      mMode = mode;
      mTime = time;
      mRejectTime = rejectTime;
      mDuration = duration;
      mProxyUid = proxyUid;
      mProxyPackageName = proxyPackage;
    }

    public int getOp() {
      return mOp;
    }

    public int getMode() {
      return mMode;
    }

    public long getTime() {
      return mTime;
    }

    public long getRejectTime() {
      return mRejectTime;
    }

    public boolean isRunning() {
      return mDuration == -1;
    }

    public int getDuration() {
      return mDuration == -1 ? (int) (System.currentTimeMillis() - mTime) : mDuration;
    }

    public int getProxyUid() {
      return mProxyUid;
    }

    public String getProxyPackageName() {
      return mProxyPackageName;
    }

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      dest.writeInt(mOp);
      dest.writeInt(mMode);
      dest.writeLong(mTime);
      dest.writeLong(mRejectTime);
      dest.writeInt(mDuration);
      dest.writeInt(mProxyUid);
      dest.writeString(mProxyPackageName);
    }

    OpEntry(Parcel source) {
      mOp = source.readInt();
      mMode = source.readInt();
      mTime = source.readLong();
      mRejectTime = source.readLong();
      mDuration = source.readInt();
      mProxyUid = source.readInt();
      mProxyPackageName = source.readString();
    }

    public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
      @Override
      public OpEntry createFromParcel(Parcel source) {
        return new OpEntry(source);
      }

      @Override
      public OpEntry[] newArray(int size) {
        return new OpEntry[size];
      }
    };
  }
} public class ParceledListSlice<T extends Parcelable>{
  public List<T> getList() {
    return null;
  }

  public void writeToParcel(Parcel dest, int flags) {

  }
} public class ServiceManager {

  public static IBinder getService(String name) {
    return null;
  }
} public interface IAppOpsService {
  int checkOperation(int code, int uid, String packageName);
  int noteOperation(int code, int uid, String packageName);
  int permissionToOpCode(String permission);

  // Remaining methods are only used in Java.
  int checkPackage(int uid, String packageName);
  List<Parcelable> getPackagesForOps( int[] ops);
  List<Parcelable> getOpsForPackage(int uid, String packageName, int[] ops);
  void setUidMode(int code, int uid, int mode);
  void setMode(int code, int uid, String packageName, int mode);
  void resetAllModes(int reqUserId, String reqPackageName);



  abstract class Stub{
    public static IAppOpsService asInterface(android.os.IBinder obj){
      return null;
    }
  }
} public abstract class ClassCallerProcessor {
    private Context mPackageContext;
    private Context mSystemContext;
    private ServerRunInfo mServerRunInfo;

  public ClassCallerProcessor(Context mPackageContext, Context mSystemContext,
      byte[] bytes) {
    this.mPackageContext = mPackageContext;
    this.mSystemContext = mSystemContext;
    this.mServerRunInfo = ParcelableUtil.unmarshall(bytes,ServerRunInfo.CREATOR);
  }

  protected ServerRunInfo getServerRunInfo() {
        return mServerRunInfo;
    }

    protected Context getSystemContext(){
        return mSystemContext;
    }

    /**
     * get current package context
     * @return
     */
    protected Context getPackageContext() {
        return mPackageContext;
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result. This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     * <p>
     * <strong>Note:</strong>This method invoke on ROOT process, DON'T across processes access object !
     *
     * @param args
     * @return put your result in Bundle
     * @throws Throwable
     */
    public abstract Bundle proxyInvoke(Bundle args) throws Throwable;
} public class FLog {

  public static boolean writeLog = false;
  private static FileOutputStream fos;
  private static AtomicInteger sBufferSize = new AtomicInteger();
  private static AtomicInteger sErrorCount = new AtomicInteger();

  private static void openFile() {
    try {
      if (writeLog && fos == null && sErrorCount.get() < 5) {
        File file = new File("/data/local/tmp/opsx.txt");
        fos = new FileOutputStream(file);

        fos.write("\n\n\n--------------------".getBytes());
        fos.write(new Date().toString().getBytes());
        fos.write("\n\n".getBytes());
        chown(file.getAbsolutePath(),2000,2000);
        chmod(file.getAbsolutePath(),0755);
      }
    } catch (Exception e) {
      e.printStackTrace();
      sErrorCount.incrementAndGet();
      fos = null;
    }
  }

  private static void chown(String path, int uid, int gid){
    try {
      if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
        Os.chown(path,uid,gid);
      }else {
        Runtime.getRuntime().exec("chown "+uid+":"+gid+" "+path).destroy();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void chmod(String path,int mode){
    try {
      if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
        Os.chmod(path, mode);
      }else {
        Runtime.getRuntime().exec("chmod "+mode+" "+path).destroy();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static void log(String log) {
    if (writeLog) {
      System.out.println(log);
    } else {
      Log.e("appopsx", "Flog --> " + log);
    }

    try {
      if (writeLog) {
        openFile();
        if (fos != null) {
          fos.write(log.getBytes());
          fos.write("\n".getBytes());

          if (sBufferSize.incrementAndGet() > 10) {
            fos.getFD().sync();
            fos.flush();
            sBufferSize.set(0);
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static void log(Throwable e) {
    log(Log.getStackTraceString(e));
  }

  public static void close() {
    try {
      if (writeLog && fos != null) {
        fos.getFD().sync();
        fos.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

} public class OpEntry implements Parcelable {

  private final int mOp;
  private final int mMode;
  private final long[] mTimes;
  private final long[] mRejectTimes;
  private final int mDuration;
  private final int mProxyUid;
  private final boolean mRunning;
  private final String mProxyPackageName;

  /**
   * {@see https://github.com/LineageOS/android_frameworks_base/blob/lineage-16.0/core/java/android/app/AppOpsManager.java}
   */
  private final int mAllowedCount;
  private final int mIgnoredCount;

  // Metrics about an op when its uid is persistent.
  public static final int UID_STATE_PERSISTENT = 0;
  // Metrics about an op when its uid is at the top.
  public static final int UID_STATE_TOP = 1;
  // Metrics about an op when its uid is running a foreground service.
  public static final int UID_STATE_FOREGROUND_SERVICE = 2;
  // Last UID state in which we don't restrict what an op can do.
  public static final int UID_STATE_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE;
  // Metrics about an op when its uid is in the foreground for any other reasons.
  public static final int UID_STATE_FOREGROUND = 3;
  // Metrics about an op when its uid is in the background for any reason.
  public static final int UID_STATE_BACKGROUND = 4;
  // Metrics about an op when its uid is cached.
  public static final int UID_STATE_CACHED = 5;
  // Number of uid states we track.
  public static final int _NUM_UID_STATE = 6;

  public OpEntry(int op, int mode, long time, long rejectTime, int duration,
      int proxyUid, String proxyPackage,int allowedCount,int ignoredCount) {
    mOp = op;
    mMode = mode;
    mTimes = new long[_NUM_UID_STATE];
    mRejectTimes = new long[_NUM_UID_STATE];
    mTimes[0] = time;
    mRejectTimes[0] = rejectTime;
    mDuration = duration;
    mRunning = duration == -1;
    mProxyUid = proxyUid;
    mProxyPackageName = proxyPackage;
    mAllowedCount = allowedCount;
    mIgnoredCount = ignoredCount;
  }

  public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
                 boolean running, int proxyUid, String proxyPackage,
                 int allowedCount, int ignoredCount) {
    mOp = op;
    mMode = mode;
    mTimes = new long[_NUM_UID_STATE];
    mRejectTimes = new long[_NUM_UID_STATE];
    System.arraycopy(times, 0, mTimes, 0, _NUM_UID_STATE);
    System.arraycopy(rejectTimes, 0, mRejectTimes, 0, _NUM_UID_STATE);
    mDuration = duration;
    mRunning = running;
    mProxyUid = proxyUid;
    mProxyPackageName = proxyPackage;
    mAllowedCount = allowedCount;
    mIgnoredCount = ignoredCount;
  }

  public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
                 int proxyUid, String proxyPackage, int allowedCount, int ignoredCount) {
    this(op, mode, times, rejectTimes, duration, duration == -1, proxyUid, proxyPackage,
            allowedCount, ignoredCount);
  }

  public OpEntry(int op, int mode, long time, long rejectTime, int duration,
                 int proxyUid, String proxyPackage) {
    this(op, mode, time, rejectTime, duration, proxyUid, proxyPackage,
            0, 0);
  }


  public int getOp() {
    return mOp;
  }

  public int getMode() {
    return mMode;
  }

  public long getTime() {
    return maxTime(mTimes, 0, _NUM_UID_STATE);
  }

  public long getRejectTime() {
    return maxTime(mRejectTimes, 0, _NUM_UID_STATE);
  }

  public boolean isRunning() {
    return mDuration == -1;
  }

  public int getDuration() {
    return mDuration;
  }

  public int getProxyUid() {
    return mProxyUid;
  }

  public String getProxyPackageName() {
    return mProxyPackageName;
  }

  public int getAllowedCount() {
    return mAllowedCount;
  }

  public int getIgnoredCount() {
    return mIgnoredCount;
  }

  @Override
  public String toString() {
    return "OpEntry{" +
        "mOp=" + mOp +
        ", mMode=" + mMode +
        ", mTime=" + Arrays.toString(mTimes) +
        ", mRejectTime=" + Arrays.toString(mRejectTimes) +
        ", mDuration=" + mDuration +
        ", mProxyUid=" + mProxyUid +
        ", mProxyPackageName='" + mProxyPackageName + '\'' +
        '}';
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(this.mOp);
    dest.writeInt(this.mMode);
    dest.writeLongArray(this.mTimes);
    dest.writeLongArray(this.mRejectTimes);
    dest.writeInt(this.mDuration);
    dest.writeByte((byte) (this.mRunning ? 1 : 0));
    dest.writeInt(this.mProxyUid);
    dest.writeString(this.mProxyPackageName);
    dest.writeInt(this.mAllowedCount);
    dest.writeInt(this.mIgnoredCount);
  }

  protected OpEntry(Parcel in) {
    this.mOp = in.readInt();
    this.mMode = in.readInt();
    this.mTimes = in.createLongArray();
    this.mRejectTimes = in.createLongArray();
    this.mDuration = in.readInt();
    this.mRunning = in.readByte() != 0;
    this.mProxyUid = in.readInt();
    this.mProxyPackageName = in.readString();
    this.mAllowedCount = in.readInt();
    this.mIgnoredCount = in.readInt();
  }

  public static long maxTime(long[] times, int start, int end) {
    long time = 0;
    for (int i = start; i < end; i++) {
      if (times[i] > time) {
        time = times[i];
      }
    }
    return time;
  }

  public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
    @Override
    public OpEntry createFromParcel(Parcel source) {
      return new OpEntry(source);
    }

    @Override
    public OpEntry[] newArray(int size) {
      return new OpEntry[size];
    }
  };
} public class OpsCommands {

  public static final String ACTION_GET = "get";
  public static final String ACTION_SET = "set";
  public static final String ACTION_RESET = "reset";
  public static final String ACTION_GET_FOR_OPS ="get_f_ops";
  public static final String ACTION_GET_APPS = "get_apps";
  public static final String ACTION_OTHER = "other";

  public static class Builder implements Parcelable {

    private String action = ACTION_GET;
    private String packageName;
    private int userHandleId;
    private int opInt;
    private int modeInt;
    private int[] ops;
    private boolean reqNet;

    public String getAction() {
      return action;
    }

    public Builder setAction(String action) {
      this.action = action;
      return this;
    }

    public String getPackageName() {
      return packageName;
    }

    public Builder setPackageName(String packageName) {
      this.packageName = packageName;
      return this;
    }

    public int getOpInt() {
      return opInt;
    }

    public Builder setOpInt(int opInt) {
      this.opInt = opInt;
      return this;
    }

    public int getModeInt() {
      return modeInt;
    }

    public Builder setModeInt(int modeInt) {
      this.modeInt = modeInt;
      return this;
    }

    public int getUserHandleId() {
      return userHandleId;
    }

    public Builder setUserHandleId(int uid) {
      this.userHandleId = uid;
      return this;
    }

    public int[] getOps() {
      return ops;
    }

    public Builder setOps(int[] ops) {
      this.ops = ops;
      return this;
    }

    public boolean isReqNet() {
      return reqNet;
    }

    public Builder setReqNet(boolean reqNet) {
      this.reqNet = reqNet;
      return this;
    }

    @Override
    public String toString() {
      return "Builder{" +
          "action='" + action + '\'' +
          ", packageName='" + packageName + '\'' +
          ", userHandleId=" + userHandleId +
          ", opInt=" + opInt +
          ", modeInt=" + modeInt +
          '}';
    }

    public Builder() {
    }

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      dest.writeString(this.action);
      dest.writeString(this.packageName);
      dest.writeInt(this.userHandleId);
      dest.writeInt(this.opInt);
      dest.writeInt(this.modeInt);
      dest.writeIntArray(this.ops);
      dest.writeByte(this.reqNet ? (byte) 1 : (byte) 0);
    }

    protected Builder(Parcel in) {
      this.action = in.readString();
      this.packageName = in.readString();
      this.userHandleId = in.readInt();
      this.opInt = in.readInt();
      this.modeInt = in.readInt();
      this.ops = in.createIntArray();
      this.reqNet = in.readByte() != 0;
    }

    public static final Creator<Builder> CREATOR = new Creator<Builder>() {
      @Override
      public Builder createFromParcel(Parcel source) {
        return new Builder(source);
      }

      @Override
      public Builder[] newArray(int size) {
        return new Builder[size];
      }
    };
  }
} public class OpsResult implements Parcelable {

  private Throwable exception;
  private List<PackageOps> list;

  public OpsResult(List<PackageOps> list, Throwable exception) {
    this.exception = exception;
    this.list = list;
  }


  public Throwable getException() {
    return exception;
  }

  public List<PackageOps> getList() {
    return list;
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeSerializable(this.exception);
    dest.writeTypedList(this.list);
  }

  protected OpsResult(Parcel in) {
    this.exception = (Exception) in.readSerializable();
    this.list = in.createTypedArrayList(PackageOps.CREATOR);
  }

  public static final Parcelable.Creator<OpsResult> CREATOR = new Parcelable.Creator<OpsResult>() {
    @Override
    public OpsResult createFromParcel(Parcel source) {
      return new OpsResult(source);
    }

    @Override
    public OpsResult[] newArray(int size) {
      return new OpsResult[size];
    }
  };


  @Override
  public String toString() {
    return "OpsResult{" +
        "exception=" + exception +
        ", list=" + list +
        '}';
  }
} public final class OtherOp {

  public static final int OP_ACCESS_PHONE_DATA = 51001;

  public static final int OP_ACCESS_WIFI_NETWORK = 52002;


  public static final String OP_NAME_ACCESS_PHONE_DATA = "ACCESS_PHONE_DATA";
  public static final String OP_NAME_ACCESS_WIFI_NETWORK = "ACCESS_WIFI_NETWORK";

  private static Boolean sSupportCount = null;

  private static Integer s_OP_WIFI_SCAN = null;

  private static final SparseArray<String> mData = new SparseArray<>();
  private static final SparseArray<String> mPerms = new SparseArray<>();

  static {
    mData.put(OP_ACCESS_PHONE_DATA, OP_NAME_ACCESS_PHONE_DATA);
    mData.put(OP_ACCESS_WIFI_NETWORK, OP_NAME_ACCESS_WIFI_NETWORK);

    mPerms.put(OP_ACCESS_PHONE_DATA, Manifest.permission.INTERNET);
    mPerms.put(OP_ACCESS_WIFI_NETWORK, Manifest.permission.INTERNET);
  }

  public static boolean isOtherOp(int op) {
    return op == OP_ACCESS_PHONE_DATA || op == OP_ACCESS_WIFI_NETWORK;
  }

  public static boolean isOtherOp(String opName) {
    return OP_NAME_ACCESS_PHONE_DATA.equals(opName) || OP_NAME_ACCESS_WIFI_NETWORK.equals(opName);
  }


  public static String getOpName(int op) {
    return mData.get(op);
  }

  public static String getOpPermName(int op) {
    return mPerms.get(op);
  }

  public static boolean isSupportCount(){
    if(sSupportCount == null){
      try {
        Class<?> aClass = Class.forName("android.app.AppOpsManager$OpEntry", false,
            ClassLoader.getSystemClassLoader());
        sSupportCount = ReflectUtils.hasField(aClass,"mAllowedCount");
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    }
    return sSupportCount;
  }

  public static int getWifiScanOp() {
    if (s_OP_WIFI_SCAN == null) {
      s_OP_WIFI_SCAN = (Integer) ReflectUtils.getFieldValue(AppOpsManager.class, "OP_WIFI_SCAN");
    }
    return s_OP_WIFI_SCAN != null ? s_OP_WIFI_SCAN : -1;
  }

} public class PackageOps implements Parcelable {

  private final String mPackageName;
  private final int mUid;
  private final List<OpEntry> mEntries;
  private SparseArray<OpEntry> mSparseEntries = null;

  public PackageOps(String packageName, int uid, List<OpEntry> entries) {
    mPackageName = packageName;
    mUid = uid;
    mEntries = entries;
  }

  public String getPackageName() {
    return mPackageName;
  }

  public int getUid() {
    return mUid;
  }

  public List<OpEntry> getOps() {
    return mEntries;
  }

  public boolean hasOp(int op) {
    if (mSparseEntries == null) {
      mSparseEntries = new SparseArray<>();
      if (mEntries != null) {
        for (OpEntry entry : mEntries) {
          mSparseEntries.put(entry.getOp(), entry);
        }
      }
    }
    return mSparseEntries.indexOfKey(op) >= 0;
  }

  @Override
  public String toString() {
    return "PackageOps{" +
        "mPackageName='" + mPackageName + '\'' +
        ", mUid=" + mUid +
        ", mEntries=" + mEntries +
        '}';
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.mPackageName);
    dest.writeInt(this.mUid);
    dest.writeList(this.mEntries);
  }

  protected PackageOps(Parcel in) {
    this.mPackageName = in.readString();
    this.mUid = in.readInt();
    this.mEntries = new ArrayList<OpEntry>();
    in.readList(this.mEntries, OpEntry.class.getClassLoader());
  }

  public static final Parcelable.Creator<PackageOps> CREATOR = new Parcelable.Creator<PackageOps>() {
    @Override
    public PackageOps createFromParcel(Parcel source) {
      return new PackageOps(source);
    }

    @Override
    public PackageOps[] newArray(int size) {
      return new PackageOps[size];
    }
  };
} public class ReflectUtils {

  private static final Map<String, Field> sFieldCache = new HashMap<String, Field>();
  private static final Map<String, Method> sMethodCache = new HashMap<String, Method>();

  public static PackageOps opsConvert(Object object) {
    String packageName = null;
    int uid = 0;
    List<OpEntry> entries = null;
    Object mPackageName = getFieldValue(object, "mPackageName");
    if (mPackageName instanceof String) {
      packageName = ((String) mPackageName);
    }

    Object mUid = getFieldValue(object, "mUid");
    if (mUid instanceof Integer) {
      uid = ((Integer) mUid);
    }

    Object mEntries = getFieldValue(object, "mEntries");
    if (mEntries instanceof List) {
      List list = (List) mEntries;
      entries = new ArrayList<>();
      for (Object o : list) {
        int mOp =  getIntFieldValue(o, "mOp");
        int mMode = getIntFieldValue(o, "mMode");
        long mTime = (Long) invokMethod(o, "getTime",null,null);
        long mRejectTime = (long)invokMethod(o, "getRejectTime",null,null);
        int mDuration = getIntFieldValue(o, "mDuration");
        int mProxyUid = getIntFieldValue(o, "mProxyUid");
        String mProxyPackageName = String.valueOf(getFieldValue(o, "mProxyPackageName"));
        if(OtherOp.isSupportCount()){
          // LineageOS
          int mAllowedCount = getIntFieldValue(o, "mAllowedCount");
          int mIgnoredCount = getIntFieldValue(o, "mIgnoredCount");
          entries.add(
              new OpEntry(mOp, mMode, mTime, mRejectTime, mDuration, mProxyUid, mProxyPackageName,mAllowedCount,mIgnoredCount));
        }else {
          entries.add(
              new OpEntry(mOp, mMode, mTime, mRejectTime, mDuration, mProxyUid, mProxyPackageName));
        }

      }
    }

    return new PackageOps(packageName, uid, entries);
  }

  public static Object getFieldValue(Object obj, String fieldName) {
    Field field = sFieldCache.get(fieldName);
    if (field == null) {
      try {
        if (obj instanceof Class) {
          field = ((Class) obj).getDeclaredField(fieldName);
        } else {
          field = obj.getClass().getDeclaredField(fieldName);
        }
        field.setAccessible(true);
        sFieldCache.put(fieldName, field);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (field != null) {
      try {
        return field.get(obj);
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  private static int getIntFieldValue(Object obj, String fieldName) {
    Object fieldValue = getFieldValue(obj, fieldName);
    if (fieldValue instanceof Integer) {
      return ((Integer) fieldValue);
    }
    return 0;
  }

  private static long getLongFieldValue(Object obj, String fieldName) {
    Object fieldValue = getFieldValue(obj, fieldName);
    if (fieldValue instanceof Long) {
      return ((Long) fieldValue);
    }
    return 0;
  }


  public static Object getArrayFieldValue(Class cls, String arrayFieldName, int index) {
    Field field = sFieldCache.get(arrayFieldName);
    if (field == null) {
      try {
        field = cls.getDeclaredField(arrayFieldName);
        field.setAccessible(true);
        sFieldCache.put(arrayFieldName, field);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (field != null) {
      try {
        Object object = field.get(cls);
        if (object.getClass().isArray()) {
          Object[] array = (Object[]) object;
          return array[index];
        }

      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  public static Object invokMethod(Object object, String methodName, List<Class> paramsTypes,
      List<Object> params) {
    StringBuilder sb = new StringBuilder(methodName);
    if (paramsTypes != null && !paramsTypes.isEmpty()) {
      sb.append("-");
      for (Class aClass : paramsTypes) {
        sb.append(aClass.getSimpleName()).append(",");
      }
    }

    Method method = sMethodCache.get(sb.toString());
    if (method == null) {
      try {
        Class cls = null;

        if (object instanceof Class) {
          //static method
          cls = ((Class) object);
        } else {
          cls = object.getClass();
        }

        if (paramsTypes != null && !paramsTypes.isEmpty()) {
          method = cls.getDeclaredMethod(methodName, paramsTypes.toArray(new Class[paramsTypes.size()]));
        } else {
          method = cls.getDeclaredMethod(methodName);

        }
        method.setAccessible(true);


        sMethodCache.put(sb.toString(), method);
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      }
    }
    if (method != null) {
      try {
        if (params != null && !params.isEmpty()) {
          return method.invoke(object, params.toArray());
        } else {
          return method.invoke(object);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return null;
  }



  public static boolean hasField(Class cls,String fieldName){
    try {
      Field field = cls.getDeclaredField(fieldName);
      return true;
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    }
    return false;
  }
} public class ServerRunInfo implements android.os.Parcelable {

  public String protocolVersion = OpsDataTransfer.PROTOCOL_VERSION;

  public String startArgs;
  public long startTime;
  public long startRealTime;
  public long recvBytes;
  public long sentBytes;

  public long successCount;
  public long errorCount;


  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.protocolVersion);
    dest.writeString(this.startArgs);
    dest.writeLong(this.startTime);
    dest.writeLong(this.startRealTime);
    dest.writeLong(this.recvBytes);
    dest.writeLong(this.sentBytes);
    dest.writeLong(this.successCount);
    dest.writeLong(this.errorCount);
  }

  public ServerRunInfo() {
  }

  protected ServerRunInfo(Parcel in) {
    this.protocolVersion = in.readString();
    this.startArgs = in.readString();
    this.startTime = in.readLong();
    this.startRealTime = in.readLong();
    this.recvBytes = in.readLong();
    this.sentBytes = in.readLong();
    this.successCount = in.readLong();
    this.errorCount = in.readLong();
  }

  public static final Creator<ServerRunInfo> CREATOR = new Creator<ServerRunInfo>() {
    @Override
    public ServerRunInfo createFromParcel(Parcel source) {
      return new ServerRunInfo(source);
    }

    @Override
    public ServerRunInfo[] newArray(int size) {
      return new ServerRunInfo[size];
    }
  };


  @Override
  public String toString() {
    return "ServerRunInfo{" +
        "protocolVersion='" + protocolVersion + '\'' +
        ", startArgs='" + startArgs + '\'' +
        ", startTime=" + startTime +
        ", startRealTime=" + startRealTime +
        ", recvBytes=" + recvBytes +
        ", sentBytes=" + sentBytes +
        ", successCount=" + successCount +
        ", errorCount=" + errorCount +
        '}';
  }
} public class Shell {

  private final static String TAG = "Shell";

  private static final String TOKEN = "ZL@LOVE^TYS"; //U+1F430 U+2764 U+1F431

  private final Process proc;
  private final BufferedReader in;
  private final OutputStream out;
  private LinkedBlockingQueue<Command> commandQueue = new LinkedBlockingQueue<Command>();

  private final AtomicInteger mNextCmdID = new AtomicInteger(0);
  private volatile boolean close = false;

  private static Shell sShell;
  private static Shell sRootShell;

  public static Shell getShell() {
    if (sShell == null) {
      synchronized (Shell.class) {
        if (sShell == null) {
          try {
            sShell = new Shell("sh");
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
    return sShell;
  }

  public static Shell getRootShell() throws IOException {
    if (sRootShell == null) {
      synchronized (Shell.class) {
        if (sRootShell == null) {
          sRootShell = openRootShell();
        }
      }
    }
    return sRootShell;
  }

  public static Shell openRootShell() throws IOException {
    return new Shell("su", true);
  }

  private Shell(String cmd, boolean checkRoot) throws IOException {

    proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
    in = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    if (checkRoot) {
      RootChecker checker = null;
      try {
        checker = new RootChecker(proc);
        checker.start();

        checker.join(20000);

        if (checker.exit == -1) {
          throw new RuntimeException("grant root timeout");
        }

        if (checker.exit != 1) {
          throw new RuntimeException(checker.errorMsg);
        }
      } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }
    }

    out = proc.getOutputStream();

    Runnable shellRunnable = new Runnable() {
      @Override
      public void run() {
        while (!close) {
          try {
            Command command = commandQueue.take();
            if (command != null && !close) {
              writeCommand(command);
              readCommand(command);
            }
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        if (close) {
          try {
            destroyShell();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    };

    new Thread(shellRunnable, "shell").start();
  }

  private Shell(String cmd) throws IOException {

    proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
    in = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    out = proc.getOutputStream();

    Runnable shellRunnable = new Runnable() {
      @Override
      public void run() {
        while (!close) {
          try {
            Command command = commandQueue.take();
            if (command != null && !close) {
              writeCommand(command);
              readCommand(command);
            }
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        if (close) {
          try {
            destroyShell();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    };

    new Thread(shellRunnable, "shell").start();
  }


  private void writeCommand(Command command) throws IOException {
    OutputStream out = this.out;
    command.writeCommand(out);
    String line = "\necho " + TOKEN + " " + command.getID() + " $?\n";
    out.write(line.getBytes());
    out.flush();
  }

  private void readCommand(Command command) throws IOException {
    if (command != null) {
      while (!close) {
        String line = in.readLine();
        if (line == null || close) {
          break;
        }
        int pos = line.indexOf(TOKEN);
        if (pos > 0) {
          command.onUpdate(command.getID(), line.substring(0, pos));
        }
        if (pos >= 0) {
          line = line.substring(pos);
          String fields[] = line.split(" ");
          if (fields.length >= 2 && fields[1] != null) {
            int id = 0;
            try {
              id = Integer.parseInt(fields[1]);
            } catch (NumberFormatException e) {
            }
            int exitCode = -1;
            try {
              exitCode = Integer.parseInt(fields[2]);
            } catch (NumberFormatException e) {
            }
            if (id == command.getID()) {
              command.setExitCode(exitCode);
              break;
            }
          }
        }
        command.onUpdate(command.getID(), line);
      }
    }
  }

  public void destroyShell() throws InterruptedException {
    //proc.waitFor();
    try {
      writeCommand(new Command("exit 33\n") {
        @Override
        public void onUpdate(int id, String message) {

        }

        @Override
        public void onFinished(int id) {

        }
      });
    } catch (Exception e) {
      e.printStackTrace();
    }
    try {
      if (in != null) {
        in.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    try {
      if (out != null) {
        out.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    if (!commandQueue.isEmpty()) {
      Command command = null;
      while ((command = commandQueue.poll()) != null) {
        command.terminate("Unexpected Termination.");
        command = null;
      }
    }

    proc.destroy();
  }

  public boolean isClose() {
    return close;
  }

  public void close() throws IOException {
    this.close = true;
  }


  /**
   * 是否所有命令处理完
   */
  public boolean allCommandsOver() {
    if (null != commandQueue && !commandQueue.isEmpty()) {
      return false;
    }
    return true;
  }


  private int generateCommandID() {
    int id = mNextCmdID.getAndIncrement();
    if (id > 0x00FFFFFF) {
      mNextCmdID.set(1);
      id = generateCommandID();
    }
    return id;
  }

  private Command add(Command command) throws IOException {
    if (close) {
      throw new IllegalStateException("Unable to add commands to a closed shell");
    }
    command.setId(generateCommandID());
    commandQueue.offer(command);
    return command;
  }

  public Result exec(String cmd) throws IOException {
    Result result = new Result();
    FLog.log("cmd  -->  " + cmd);
    final StringBuilder outLine = new StringBuilder();
    try {
      result.statusCode = add(new Command(cmd) {
        @Override
        public void onUpdate(int id, String message) {
          outLine.append(message).append('\n');
        }

        @Override
        public void onFinished(int id) {
        }
      }).waitForFinish();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    if (result.statusCode == -1) {
      try {
        outLine.setLength(0);
        result.statusCode = add(new Command(cmd) {
          @Override
          public void onUpdate(int id, String message) {
            outLine.append(message).append('\n');
          }

          @Override
          public void onFinished(int id) {
          }
        }).waitForFinish();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    result.message = outLine.toString();
    return result;
  }

  public int countCommands() {
    return commandQueue.size();
  }


  abstract class Command {

    private String[] commands;
    private boolean isFinished;
    private int exitCode;
    private long timeout;
    private int id;

    public abstract void onUpdate(int id, String message);

    public abstract void onFinished(int id);

    public Command(String... commands) {
      this(1000 * 30, commands);
    }

    public Command(int timeout, String... commands) {
      this.timeout = timeout;
      this.commands = commands;
    }

    void setId(int id) {
      this.id = id;
    }

    public int getID() {
      return id;
    }

    public void setExitCode(int code) {
      synchronized (this) {
        exitCode = code;
        isFinished = true;
        onFinished(id);
        this.notifyAll();
      }
    }

    public boolean isFinished() {
      synchronized (this) {
        return isFinished;
      }
    }

    public void terminate(String reason) {
      try {
        close();
        setExitCode(-1);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    public int waitForFinish(long timeout) throws InterruptedException {
      synchronized (this) {
        while (!isFinished) {
          this.wait(timeout);
          if (!isFinished) {
            isFinished = true;
            terminate("Timeout Exception");
          }
        }
      }
      return exitCode;
    }

    public int waitForFinish() throws InterruptedException {
      synchronized (this) {
        waitForFinish(timeout);
      }
      return exitCode;
    }

    public String getCommand() {
      if (commands == null || commands.length == 0) {
        return "";
      }

      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < commands.length; i++) {
        sb.append(commands[i]);
        sb.append('\n');
      }
      String command = sb.toString();
      return command;
    }

    public void writeCommand(OutputStream out) throws IOException {
      out.write(getCommand().getBytes());
      out.flush();
    }

  }

  public static class Result {

    private String message;
    private int statusCode = -1;

    public String getMessage() {
      return message;
    }

    public int getStatusCode() {
      return statusCode;
    }

  }


  private static class RootChecker extends Thread {

    int exit = -1;
    String errorMsg = null;
    Process process;

    private RootChecker(Process process) {
      this.process = process;
    }

    @Override
    public void run() {
      try {
        BufferedReader inputStream = new BufferedReader(
            new InputStreamReader(process.getInputStream(), "UTF-8"));
        BufferedWriter outputStream = new BufferedWriter(
            new OutputStreamWriter(this.process.getOutputStream(), "UTF-8"));

        outputStream.write("echo Started");
        outputStream.newLine();
        outputStream.flush();

        while (true) {
          String line = inputStream.readLine();
          if (line == null) {
            throw new EOFException();
          }
          if ("".equals(line)) {
            continue;
          }
          if ("Started".equals(line)) {
            this.exit = 1;
            break;
          }
          errorMsg = "unkown error occured.";
        }
      } catch (IOException e) {
        exit = -42;
        if (e.getMessage() != null) {
          errorMsg = e.getMessage();
        } else {
          errorMsg = "RootAccess denied?.";
        }
      }

    }
  }

}
import android.Manifest;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.Process;
import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.app.IAppOpsService;
import com.zzzmode.appopsx.common.ClassCallerProcessor;
import com.zzzmode.appopsx.common.FLog;
import com.zzzmode.appopsx.common.OpEntry;
import com.zzzmode.appopsx.common.OpsCommands;
import com.zzzmode.appopsx.common.OpsResult;
import com.zzzmode.appopsx.common.OtherOp;
import com.zzzmode.appopsx.common.PackageOps;
import com.zzzmode.appopsx.common.ReflectUtils;
import com.zzzmode.appopsx.common.ServerRunInfo;
import com.zzzmode.appopsx.common.Shell;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
package com.zzzmode.appopsx.remote;


public class AppOpsHandler extends ClassCallerProcessor {

  private static IptablesController mIptablesController = null;
  static {
    try {
      if(Process.myUid() == 0) {
        mIptablesController = new IptablesController(Shell.getRootShell());
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public AppOpsHandler(Context mPackageContext, Context mSystemContext,
      byte[] bytes) {
    super(mPackageContext, mSystemContext, bytes);
  }

  @Override
  public Bundle proxyInvoke(Bundle args) throws Throwable {

    OpsResult result = null;

    try {
      OpsCommands.Builder builder = args.getParcelable("args");
      args.clear();
      FLog.log(" appops "+builder);