/* * Copyright (C) 2013 - 2016 Alexander "Evisceration" Martinz * * 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 org.namelessrom.devicecontrol; import android.Manifest; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.annotation.StringRes; import android.text.TextUtils; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import org.namelessrom.devicecontrol.models.DeviceConfig; import org.namelessrom.devicecontrol.utils.AppHelper; import org.namelessrom.devicecontrol.utils.Utils; import java.util.ArrayList; import java.util.Iterator; import at.amartinz.execution.BusyBox; import at.amartinz.execution.RootCheck; import at.amartinz.hardware.device.Device; public class CheckRequirementsTask extends AsyncTask<Void, Void, Void> { private static final String XPOSED_INSTALLER_PACAKGE = "de.robv.android.xposed.installer"; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final MainActivity mainActivity; private final boolean skipChecks; private final ArrayList<Dialog> dialogs = new ArrayList<>(); private final ProgressDialog progressDialog; private AlertDialog permissionDialog; public boolean hasRoot; public boolean hasRootGranted; private boolean hasBusyBox; private String suVersion; private Runnable mPostExecuteHook; private static final String[] WHITELIST_SU = { "SUPERSU", "CM-SU" }; public CheckRequirementsTask(MainActivity mainActivity) { this.mainActivity = mainActivity; skipChecks = DeviceConfig.get().skipChecks; if (!skipChecks) { progressDialog = new ProgressDialog(mainActivity); progressDialog.setTitle(R.string.checking_requirements); progressDialog.setMessage(getString(R.string.please_wait)); progressDialog.setCancelable(false); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); } else { progressDialog = null; } } public CheckRequirementsTask setPostExecuteHook(Runnable postExecuteHook) { mPostExecuteHook = postExecuteHook; return this; } @Override protected void onPreExecute() { if (!skipChecks) { // if check takes longer than 200ms, show progress dialog mHandler.postDelayed(showDialogRunnable, 200); } } @Override protected Void doInBackground(Void... params) { if (skipChecks) { return null; } hasRoot = RootCheck.isRooted(true); if (hasRoot) { hasRootGranted = RootCheck.isRootGranted(); suVersion = RootCheck.getSuVersion(true); } hasBusyBox = BusyBox.isAvailable(true); return null; } @Override protected void onPostExecute(Void result) { // actually skip that stuff if (skipChecks) { letsGetItStarted(); return; } mHandler.removeCallbacks(showDialogRunnable); if (progressDialog != null) { progressDialog.dismiss(); } final DeviceConfig deviceConfig = DeviceConfig.get(); // if we have root and got root granted ... if (hasRoot && hasRootGranted) { // ... check if we have busybox and throw a warning, if there is no busybox if (!hasBusyBox && !deviceConfig.ignoreDialogWarningBusyBox) { dialogs.add(buildBusyBoxDialog()); } boolean showSuWarning = true; if (!TextUtils.isEmpty(suVersion) && !"-".equals(suVersion)) { final String suVersionCompare = suVersion.toUpperCase(); for (final String whitelist : WHITELIST_SU) { if (suVersionCompare.contains(whitelist)) { showSuWarning = false; } } } if (showSuWarning) { if (!deviceConfig.ignoreDialogWarningSuVersion) { dialogs.add(buildSuVersionWarning(suVersion)); } } } else { // ... else show the root warning dialog if (!deviceConfig.ignoreDialogWarningRoot) { dialogs.add(buildRootDialog()); } } letsGetItStarted(); } private String getString(@StringRes final int resId) { return mainActivity.getString(resId); } private String getString(@StringRes final int resId, Object... objects) { return mainActivity.getString(resId, objects); } private final Runnable showDialogRunnable = new Runnable() { @Override public void run() { if (progressDialog != null) { progressDialog.show(); } } }; private void letsGetItStarted() { Utils.startTaskerService(mainActivity); DeviceConfig deviceConfig = DeviceConfig.get(); if (deviceConfig.dcFirstStart) { deviceConfig.dcFirstStart = false; deviceConfig.save(); final boolean isXposedInstalled = AppHelper.isPackageInstalled(XPOSED_INSTALLER_PACAKGE); final CustomEvent customEvent = new CustomEvent("first_start"); customEvent.putCustomAttribute("xposed_installed", isXposedInstalled ? "true" : "false"); Answers.getInstance().logCustom(customEvent); } // patch sepolicy if (hasRootGranted) { Utils.patchSEPolicy(mainActivity); } for (final Dialog dialog : dialogs) { if (dialog != null) { dialog.show(); } } permissionDialog = showPermissionDialog(mainActivity); if (permissionDialog == null && mPostExecuteHook != null) { mainActivity.runOnUiThread(mPostExecuteHook); } } private AlertDialog buildBusyBoxDialog() { final String statusText = getString(R.string.warning_busybox) + "\n\n" + getString(R.string.warning_busybox_note); final AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle(R.string.missing_requirements); builder.setCancelable(false); builder.setMessage(statusText); builder.setNegativeButton(R.string.dialog_action_never_show_again, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final DeviceConfig deviceConfig = DeviceConfig.get(); deviceConfig.ignoreDialogWarningBusyBox = true; deviceConfig.save(); dialog.dismiss(); } }); builder.setNeutralButton(R.string.ignore, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.setPositiveButton(R.string.get_busybox, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { BusyBox.offerBusyBox(mainActivity); } }); return builder.create(); } private AlertDialog buildRootDialog() { final String statusText = getString(R.string.warning_root) + "\n\n" + getString(R.string.warning_root_note); final AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle(R.string.missing_requirements); builder.setCancelable(false); builder.setMessage(statusText); builder.setNegativeButton(R.string.dialog_action_never_show_again, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final DeviceConfig deviceConfig = DeviceConfig.get(); deviceConfig.ignoreDialogWarningRoot = true; deviceConfig.save(); dialog.dismiss(); } }); builder.setNeutralButton(R.string.more_information, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final String url = String.format("https://www.google.com/#q=how+to+root+%s", Device.get(mainActivity).model); ((App) mainActivity.getApplicationContext()).getCustomTabsHelper().launchUrl(mainActivity, url); } }); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return builder.create(); } private AlertDialog buildSuVersionWarning(String suVersion) { final AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); builder.setTitle(R.string.dialog_warning); builder.setMessage(getString(R.string.dialog_warning_su_version, suVersion)); builder.setNegativeButton(R.string.dialog_action_never_show_again, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final DeviceConfig deviceConfig = DeviceConfig.get(); deviceConfig.ignoreDialogWarningSuVersion = true; deviceConfig.save(); dialog.dismiss(); } }); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return builder.create(); } private AlertDialog showPermissionDialog(final Context context) { // TODO: new wizard, more user friendly if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final boolean storage = mainActivity.isGranted(Manifest.permission.READ_EXTERNAL_STORAGE) && mainActivity.isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE); final boolean telephony = mainActivity.isGranted(Manifest.permission.READ_PHONE_STATE); final boolean location = mainActivity.isGranted(Manifest.permission.ACCESS_COARSE_LOCATION) && mainActivity.isGranted(Manifest.permission.ACCESS_FINE_LOCATION); boolean needsPermissionGrant = !storage || !telephony || !location; if (needsPermissionGrant) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.dialog_permission_title); builder.setMessage(R.string.dialog_permission_summary); builder.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); final ArrayList<String> toRequest = new ArrayList<>(); if (!storage) { // we only launch this code on M anyways, but please shut up android studio if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { toRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE); } toRequest.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (!telephony) { toRequest.add(Manifest.permission.READ_PHONE_STATE); } if (!location) { toRequest.add(Manifest.permission.ACCESS_COARSE_LOCATION); toRequest.add(Manifest.permission.ACCESS_FINE_LOCATION); } mainActivity.requestPermissions(toRequest); if (mPostExecuteHook != null) { mainActivity.runOnUiThread(mPostExecuteHook); } } }); return builder.show(); } } return null; } public void destroy() { if (mainActivity != null) { mainActivity.runOnUiThread(mDestroyRunnable); } else { mDestroyRunnable.run(); } } private final Runnable mDestroyRunnable = new Runnable() { @Override public void run() { if (progressDialog != null) { progressDialog.dismiss(); } if (permissionDialog != null) { permissionDialog.dismiss(); } final Iterator<Dialog> iterator = dialogs.iterator(); while (iterator.hasNext()) { final Dialog dialog = iterator.next(); if (dialog != null) { dialog.dismiss(); } iterator.remove(); } } }; }