Java tutorial
/* * AppOpsXposed - AppOps for Android 4.3+ * Copyright (C) 2013, 2014 Joseph C. Lehner * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package at.jclehner.appopsxposed; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Parcelable; import android.support.v4.content.FileProvider; import android.util.Log; import android.widget.Toast; import at.jclehner.appopsxposed.util.AppOpsManagerWrapper; import at.jclehner.appopsxposed.util.Util; import eu.chainfire.libsuperuser.Shell; import eu.chainfire.libsuperuser.Shell.SU; public class BugReportBuilder { private Context mContext; private String mDeviceId; private String mReportTime; private File mBugReportDir; private File mBugReportFile; public static void buildAndSend(final Context context) { if (!SU.available()) { Toast.makeText(context, R.string.toast_needs_root, Toast.LENGTH_SHORT).show(); return; } Toast.makeText(context, R.string.building_toast, Toast.LENGTH_LONG).show(); final BugReportBuilder brb = new BugReportBuilder(context); new AsyncTask<Void, Void, Uri>() { @Override protected Uri doInBackground(Void... params) { return brb.build(); } @Override protected void onPostExecute(Uri result) { final ArrayList<Parcelable> uris = new ArrayList<Parcelable>(); uris.add(result); final Intent target = new Intent(Intent.ACTION_SEND_MULTIPLE); target.setType("text/plain"); target.putExtra(Intent.EXTRA_SUBJECT, "[REPORT][AppOpsXposed " + Util.getAoxVersion(context) + "] " + Build.FINGERPRINT); target.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); //target.putExtra(Intent.EXTRA_STREAM, result); target.putExtra(Intent.EXTRA_TEXT, "!!! BUG REPORTS WITHOUT ADDITIONAL INFO WILL BE IGNORED !!!"); //target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final Intent intent = Intent.createChooser(target, null); //intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); context.startActivity(intent); } }.execute(); } public BugReportBuilder(Context context) { mContext = context; mDeviceId = getDeviceId(); mReportTime = getReportTime(); mBugReportDir = new File(context.getCacheDir(), "reports"); mBugReportFile = new File(mBugReportDir, "report_" + mDeviceId + "_" + mReportTime + ".txt"); } public Uri build() { final StringBuilder sb = new StringBuilder(); sb.append("\nAOX : " + Util.getAoxVersion(mContext)); sb.append("\nTIME: " + mReportTime); sb.append("\nID : " + mDeviceId); collectDeviceInfo(sb); collectApkInfo(sb); collectAppOpsInfos(sb); Log.d("AOX", "-------------------------"); Log.d("AOX", sb.toString()); Log.d("AOX", "\n-------------------------"); collectXposedLogs(sb); collectProps(sb); collectLogcat(sb); PrintWriter pw = null; try { if (!mBugReportDir.mkdirs() && !mBugReportDir.isDirectory()) throw new RuntimeException("Failed to create " + mBugReportDir); pw = new PrintWriter(mBugReportFile); pw.println(sb); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (pw != null) pw.close(); } return FileProvider.getUriForFile(mContext, "at.jclehner.appopsxposed.files", mBugReportFile); } public Uri getBugReportFileUri() { return Uri.fromFile(mBugReportFile); } @SuppressLint("SimpleDateFormat") private String getReportTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); return sdf.format(new Date()); } private String getDeviceId() { final String raw = Build.SERIAL + Build.FINGERPRINT; byte[] bytes; try { final MessageDigest md = MessageDigest.getInstance("SHA1"); bytes = md.digest(raw.getBytes()); } catch (NoSuchAlgorithmException e) { bytes = raw.getBytes(); } final StringBuilder sb = new StringBuilder(bytes.length); for (byte b : bytes) sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); return sb.substring(0, 12); } private void collectDeviceInfo(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n------------------- DEVICE INFO -------------------"); sb.append("\nAndroid version: " + Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")"); sb.append("\nFingerprint : " + Build.FINGERPRINT); sb.append("\nDevice name : " + Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.PRODUCT + "/" + Build.HARDWARE + ")"); sb.append("\nAppOpsManager : "); try { Class.forName("android.app.AppOpsManager"); sb.append("YES"); } catch (ClassNotFoundException e) { sb.append("NO!"); } } private void collectApkInfo(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n--------------------- APK INFO --------------------"); final Intent intent = new Intent(); intent.setAction("android.settings.SETTINGS"); final HashMap<String, List<String>> appMap = new HashMap<String, List<String>>(); final List<ResolveInfo> rInfos = mContext.getPackageManager().queryIntentActivities(intent, PackageManager.GET_DISABLED_COMPONENTS); for (ResolveInfo rInfo : rInfos) { final ActivityInfo aInfo = rInfo.activityInfo; final String key = aInfo.applicationInfo.sourceDir + " (" + aInfo.packageName + ")" + toTickedBox(rInfo.activityInfo.applicationInfo.enabled); final List<String> activityList; if (!appMap.containsKey(key)) { activityList = new ArrayList<String>(); appMap.put(key, activityList); } else activityList = appMap.get(key); activityList.add(aInfo.name + toTickedBox(aInfo.enabled)); } for (String key : appMap.keySet()) { sb.append("\n" + key); for (String activity : appMap.get(key)) sb.append("\n " + activity); } } private void collectAppOpsInfos(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n------------------- APPOPS INFO -------------------\n"); if (AppOpsManagerWrapper._NUM_OP >= 0) { sb.append("\n_NUM_OP: " + AppOpsManagerWrapper._NUM_OP); for (int op = 0; op != AppOpsManagerWrapper._NUM_OP; ++op) { sb.append("\n OP_" + AppOpsManagerWrapper.opToName(op) + " = " + op); try { final int mode = AppOpsManagerWrapper.opToDefaultMode(op); if (mode != AppOpsManagerWrapper.MODE_ALLOWED) sb.append("\n default: " + AppOpsManagerWrapper.modeToName(mode)); } catch (Exception e) { // ignore } } } } private void collectXposedLogs(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n------------------- XPOSED LOGS -------------------\n"); runAsRoot(sb, "cat /data/data/de.robv.android.xposed.installer/log/error.log"); } private void collectProps(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n-------------------- BUILD.PROP -------------------\n"); runAsRoot(sb, "cat /system/build.prop"); } private void collectLogcat(StringBuilder sb) { sb.append("\n---------------------------------------------------"); sb.append("\n---------------------- LOGCAT ---------------------\n"); runAsRoot(sb, "logcat -d -v time"); } private void runAsRoot(StringBuilder sb, String command) { for (String line : Shell.SU.run(command)) sb.append(line + "\n"); } private static String toTickedBox(boolean b) { return b ? " [*]" : " [ ]"; } }