/*
* Copyright (C) 2013 - 2014 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.utils;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.namelessrom.devicecontrol.App;
import org.namelessrom.devicecontrol.Constants;
import org.namelessrom.devicecontrol.R;
import org.namelessrom.devicecontrol.models.TaskerConfig;
import org.namelessrom.devicecontrol.modules.tasker.TaskerItem;
import org.namelessrom.devicecontrol.services.TaskerService;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import at.amartinz.execution.BusyBox;
import at.amartinz.execution.Command;
import at.amartinz.execution.NormalShell;
import at.amartinz.execution.RootShell;
import timber.log.Timber;
import static org.namelessrom.devicecontrol.utils.ShellOutput.OnShellOutputListener;
public class Utils {
private static final String[] BLACKLIST = App.get().getStringArray(R.array.file_black_list);
private static final String[] ENABLED_STATES = { "Y", "TRUE", "1", "255" };
public static boolean isNameless(Context context) {
return context.getPackageManager().hasSystemFeature("org.namelessrom.android")
|| existsInFile(Scripts.BUILD_PROP, "ro.nameless.version");
}
public static boolean existsInFile(final String file, final String prop) {
return !findPropValue(file, prop).isEmpty();
}
public static String findPropValue(final String file, final String prop) {
try {
return findPropValueOf(file, prop);
} catch (Exception e) { return ""; }
}
private static String findPropValueOf(final String file, final String prop) throws Exception {
final File f = new File(file);
if (f.exists() && f.canRead()) {
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream(f);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
String s;
while ((s = br.readLine()) != null) {
if (s.contains(prop)) { return s.replace(prop + '=', ""); }
}
} finally {
Utils.closeQuietly(br);
Utils.closeQuietly(isr);
Utils.closeQuietly(fis);
}
}
return "";
}
public static String loadFromAssets(final String path) throws Exception {
final StringBuilder sb = new StringBuilder();
InputStream htmlStream = null;
InputStreamReader reader = null;
BufferedReader br = null;
try {
htmlStream = App.get().getAssets().open(path);
reader = new InputStreamReader(htmlStream);
br = new BufferedReader(reader);
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append('\n');
}
} finally {
Utils.closeQuietly(br);
Utils.closeQuietly(reader);
Utils.closeQuietly(htmlStream);
}
return sb.toString();
}
/**
* Reads a single line from a file.
*
* @param sFile The file to read from.
* @return The read string OR null if not existing.
*/
public static String readOneLine(final String sFile) {
return readOneLine(sFile, false);
}
@Nullable public static String readOneLine(final String sFile, final boolean trim) {
if (fileExists(sFile)) {
FileReader fileReader = null;
BufferedReader brBuffer = null;
try {
fileReader = new FileReader(sFile);
brBuffer = new BufferedReader(fileReader, 512);
final String value = brBuffer.readLine();
return ((trim && value != null) ? value.trim() : value);
} catch (Exception e) {
if (e instanceof FileNotFoundException) {
Timber.v("file exists but can not be read, trying with root");
} else {
Timber.e(e, "could not read file: %s", sFile);
}
return readFileViaShell(sFile, true);
} finally {
Utils.closeQuietly(brBuffer);
Utils.closeQuietly(fileReader);
}
} else {
Timber.v("File does not exist or is not readable -> %s", sFile);
}
return null;
}
/**
* Reads a file.
*
* @param sFile The file to read from.
* @return The read string OR null if not existing.
*/
@Nullable public static String readFile(final String sFile) {
if (fileExists(sFile)) {
FileReader reader = null;
BufferedReader brBuffer = null;
try {
reader = new FileReader(sFile);
brBuffer = new BufferedReader(reader, 512);
final StringBuilder sb = new StringBuilder();
String s;
while ((s = brBuffer.readLine()) != null) {
sb.append(s).append('\n');
}
return sb.toString();
} catch (Exception e) {
return readFileViaShell(sFile, true);
} finally {
Utils.closeQuietly(brBuffer);
Utils.closeQuietly(reader);
}
} else {
Timber.v("File does not exist or is not readable -> %s", sFile);
}
return null;
}
public static String readFileViaShell(final String filePath, final boolean useSu) {
final Command command = new Command(String.format("cat %s;", filePath));
return useSu ? RootShell.fireAndBlockString(command) : NormalShell.fireAndBlock(command);
}
/**
* Write a string value to the specified file.
*
* @param filename The filename
* @param value The value
*/
public static boolean writeValue(final String filename, final String value) {
if (fileExists(filename)) {
try {
final FileWriter fw = new FileWriter(filename);
//noinspection TryFinallyCanBeTryWithResources
try {
fw.write(value);
} finally {
Utils.closeQuietly(fw);
}
} catch (IOException ignored) {
writeValueViaShell(filename, value);
return false;
}
}
return true;
}
/**
* Fallback if everything fails
*
* @param filename The file to write
* @param value The value to write
*/
private static void writeValueViaShell(final String filename, final String value) {
RootShell.fireAndForget(Utils.getWriteCommand(filename, value));
}
/**
* Check if the specified file exists.
*
* @param filename The filename
* @return Whether the file exists or not
*/
public static boolean fileExists(final String filename) {
//noinspection SimplifiableIfStatement
if (TextUtils.isEmpty(filename) || TextUtils.equals("-", filename)) {
return false;
}
return new File(filename).exists();
}
/**
* Check if one of the specified files exists.
*
* @param files The list of filenames
* @return Whether one of the files exists or not
*/
public static boolean fileExists(final String[] files) {
for (final String s : files) { if (fileExists(s)) { return true; } }
return false;
}
/**
* Checks if the given paths in a string array are existing and returns the existing path.
*
* @param paths The string array, containing the file paths
* @return The path of the existing file as string
*/
public static String checkPaths(final String[] paths) {
for (final String s : paths) {
if (fileExists(s)) { return s; }
}
return "";
}
/**
* Checks if the given path is existing and returns the existing path.
*
* @param path The file path
* @return The path or an empty string if not existent
*/
public static String checkPath(final String path) {
if (fileExists(path)) { return path; }
return "";
}
/**
* Reads string array from file
*
* @param path File to read from
* @return string array
*/
@Nullable public static String[] readStringArray(final String path) {
final String line = readOneLine(path);
if (line != null) {
return line.split(" ");
}
return null;
}
public static String[] listFiles(final String path, final boolean blacklist) {
return listFiles(path, blacklist ? BLACKLIST : null);
}
public static String[] listFiles(final String path, final String[] blacklist) {
final String output = RootShell.fireAndBlockStringNewline(String.format("ls %s", path));
Timber.v("listFiles --> output: %s", output);
if (TextUtils.isEmpty(output)) {
return Constants.EMPTY_STRINGS;
}
final String[] files = output.trim().split("\n");
if (blacklist != null) {
final ArrayList<String> filtered = new ArrayList<>();
for (final String s : files) {
if (!Utils.isFileBlacklisted(s, blacklist)) {
filtered.add(s);
}
}
return filtered.toArray(new String[filtered.size()]);
}
return files;
}
public static boolean isFileBlacklisted(final String file, final String[] blacklist) {
for (final String s : blacklist) {
if (TextUtils.equals(s, file)) {
return true;
}
}
return false;
}
public static String getFileName(final String path) {
if (TextUtils.isEmpty(path)) {
return "";
}
final String[] splitted = path.trim().split("/");
Timber.v("getFileName(%s) --> %s", path, splitted[splitted.length - 1]);
return splitted[splitted.length - 1];
}
public static void getCommandResult(final OnShellOutputListener listener, final String cmd) {
getCommandResult(listener, -1, cmd);
}
public static void getCommandResult(final OnShellOutputListener listener, final int id, final String cmd) {
getCommandResult(listener, id, cmd, false);
}
public static void getCommandResult(final OnShellOutputListener listener, final int id, String cmd, boolean NEWLINE) {
final Command command = new Command(id, cmd) {
@Override public void onCommandCompleted(final int id, int exitcode) {
super.onCommandCompleted(id, exitcode);
final String result = getOutput();
App.HANDLER.post(new CommandListenerRunnable(listener, id, result));
}
};
if (NEWLINE) {
command.setOutputType(Command.OUTPUT_STRING_NEWLINE);
} else {
command.setOutputType(Command.OUTPUT_STRING);
}
RootShell.fireAndBlock(command);
}
public static String getReadCommand(final String path) {
return String.format("cat %s 2> /dev/null", path);
}
public static String getWriteCommand(final String path, final String value) {
return String.format("chmod 644 %s;", path) +
String.format("echo \"%s\" > %s;", value, path);
}
public static String lockFile(String path) {
return String.format("chmod 444 %s;", path);
}
public static void toggleComponent(final ComponentName component, final boolean disable) {
final PackageManager pm = App.get().getPackageManager();
if (pm != null) {
pm.setComponentEnabledSetting(component,
(disable
? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
: PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
PackageManager.DONT_KILL_APP
);
}
}
public static boolean startTaskerService(Context context) {
TaskerConfig taskerConfig = TaskerConfig.get();
if (!taskerConfig.enabled) {
return false;
}
boolean enabled = false;
final List<TaskerItem> taskerItemList = taskerConfig.items;
for (final TaskerItem item : taskerItemList) {
if (item.enabled) {
enabled = true;
break;
}
}
final Intent tasker = new Intent(context, TaskerService.class);
if (enabled) {
tasker.setAction(TaskerService.ACTION_START);
} else {
tasker.setAction(TaskerService.ACTION_STOP);
}
context.startService(tasker);
return enabled;
}
public static void stopTaskerService(Context context) {
final Intent tasker = new Intent(context, TaskerService.class);
tasker.setAction(TaskerService.ACTION_STOP);
context.startService(tasker);
}
public static boolean isEnabled(String s, final boolean contains) {
if (s != null) {
s = s.trim().toUpperCase();
for (final String state : ENABLED_STATES) {
if (contains) {
if (s.contains(state)) { return true; }
} else {
if (s.equals(state)) { return true; }
}
}
}
return false;
}
public static String getBatteryHealth(final int healthInt) {
int health;
switch (healthInt) {
case BatteryManager.BATTERY_HEALTH_COLD:
health = R.string.cold;
break;
case BatteryManager.BATTERY_HEALTH_GOOD:
health = R.string.good;
break;
case BatteryManager.BATTERY_HEALTH_DEAD:
health = R.string.dead;
break;
case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
health = R.string.over_voltage;
break;
case BatteryManager.BATTERY_HEALTH_OVERHEAT:
health = R.string.overheat;
break;
default:
case BatteryManager.BATTERY_HEALTH_UNKNOWN:
case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
health = R.string.unknown;
break;
}
return App.get().getString(health);
}
public static void remount(final String path, final String mode) {
final String args = String.format("-o %s,remount %s;", mode, path);
final String cmd = BusyBox.callBusyBoxApplet("mount", args);
// we need to wait for the remount to be done
RootShell.fireAndBlock(cmd);
}
public static String setPermissions(final String path, final String mask, final int user, final int group) {
final String chownArgs = String.format("%s.%s %s;", user, group, path);
final String chmodArgs = String.format("%s %s;", mask, path);
return BusyBox.callBusyBoxApplet("chown", chownArgs) + BusyBox.callBusyBoxApplet("chmod", chmodArgs);
}
public static void restartActivity(final Activity activity) {
if (activity == null) {
return;
}
activity.finish();
activity.startActivity(activity.getIntent());
}
public static Integer tryValueOf(final String value, final int def) {
try { return Integer.valueOf(value); } catch (Exception exc) { return def; }
}
@SuppressLint("SimpleDateFormat")
public static String getDateAndTime() {
final Date date = new Date(System.currentTimeMillis());
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd.HH.mm.ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat.format(date);
}
public static int parseInt(final String integer) {
return parseInt(integer, -1);
}
public static int parseInt(String integer, final int def) {
try {
if (integer != null) { integer = integer.trim(); }
return Integer.parseInt(integer);
} catch (NumberFormatException exc) {
Timber.e(exc, "parseInt(%s, %s)", integer, def);
return def;
}
}
public static void closeQuietly(final Object closeable) {
if (closeable instanceof Flushable) {
try {
((Flushable) closeable).flush();
} catch (IOException ignored) { }
}
if (closeable instanceof Closeable) {
try {
((Closeable) closeable).close();
} catch (IOException ignored) { }
}
}
public static void patchSEPolicy(Context context) {
StringBuilder sb = new StringBuilder();
// supolicy --live "allow untrusted_app proc_touchpanel dir { search }"
sb.append("supolicy --live \"allow untrusted_app proc_touchpanel dir { search }\";");
if (!Utils.isNameless(context)) {
// supolicy --live "allow platform_app proc_touchpanel dir { search }"
sb.append("supolicy --live \"allow platform_app proc_touchpanel dir { search }\";");
}
RootShell.fireAndForget(sb.toString());
}
private static class CommandListenerRunnable implements Runnable {
private final OnShellOutputListener listener;
private final int id;
private final String result;
public CommandListenerRunnable(OnShellOutputListener listener, int id, String result) {
this.listener = listener;
this.id = id;
this.result = result;
}
@Override public void run() {
if (listener != null) {
listener.onShellOutput(new ShellOutput(id, result));
}
}
}
}