/*
* 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.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;
import org.namelessrom.devicecontrol.App;
import org.namelessrom.devicecontrol.R;
import org.namelessrom.devicecontrol.modules.appmanager.PackageStatsObserver;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.List;
import at.amartinz.execution.BusyBox;
import at.amartinz.execution.RootShell;
import at.amartinz.hardware.ProcessManager;
import hugo.weaving.DebugLog;
import timber.log.Timber;
import static org.namelessrom.devicecontrol.DeviceConstants.ID_PGREP;
import static org.namelessrom.devicecontrol.utils.ShellOutput.OnShellOutputListener;
/**
* Helper class for application interactions like cleaning the cache
*/
public class AppHelper {
public static boolean preventOnResume = false;
/**
* Gets the package stats of the given application.
* The package stats are getting sent via OTTO
*
* @param pkg The package name of the application
*/
public static void getSize(final PackageManager pm, final PackageStatsObserver.OnPackageStatsListener l, final String pkg) {
try {
Method m = pm.getClass().getMethod("getPackageSizeInfo", String.class, IPackageStatsObserver.class);
m.invoke(pm, pkg, new PackageStatsObserver(l));
} catch (Exception e) {
Timber.e(e, "AppHelper.getSize()");
}
}
/**
* Clears cache via shell of the given package name
*
* @param pkg The package name of the application
*/
public static void clearCache(final PackageManager pm, final String pkg) {
deleteCacheOrData(pm, pkg, true);
}
/**
* Clears data via shell of the given package name
*
* @param pkg The package name of the application
*/
public static void clearData(final PackageManager pm, final String pkg) {
deleteCacheOrData(pm, pkg, false);
}
private static void deleteCacheOrData(PackageManager pm, String pkg, boolean clearCache) {
// internal (/data)
final String internal;
// external (/sdcard/Android)
final String external;
final String method;
final String cmdPrefix;
final String internalBase = String.format("rm -rf /data/data/%s", pkg);
final String externalBase = String.format("rm -rf %s/Android/data/%s",
Environment.getExternalStorageDirectory().getAbsolutePath(), pkg);
if (clearCache) {
// 3 x base
final String dirs = "%s/app_*/*;%s/cache/*;%s/code_cache/*;";
method = "deleteApplicationCacheFiles";
cmdPrefix = "";
internal = String.format(dirs, internalBase, internalBase, internalBase);
external = String.format(dirs, externalBase, externalBase, externalBase);
} else {
// 5 x base
final String dirs = "%s/app_*;%s/cache;%s/databases;%s/files;%s/shared_prefs;";
method = "clearApplicationUserData";
cmdPrefix = String.format("pkill -TERM %s;pm clear %s;sync;", pkg, pkg);
internal = String.format(dirs, internalBase, internalBase, internalBase, internalBase, internalBase);
external = String.format(dirs, externalBase, externalBase, externalBase, externalBase, externalBase);
}
Timber.d("internal -> %s", internal);
Timber.d("external -> %s", external);
try {
final Method m = pm.getClass().getDeclaredMethod(method, String.class, IPackageDataObserver.class);
m.invoke(pm, pkg, null);
} catch (Exception e) {
Timber.e(e, "could not call %s via reflection", method);
}
RootShell.fireAndForget(String.format("%s%s%s;sync;", cmdPrefix, internal, external));
}
public static void uninstallPackage(PackageManager pm, String pkg) {
try {
Method m = pm.getClass().getDeclaredMethod("deletePackage", String.class, IPackageDeleteObserver.class, int.class);
m.invoke(pm, pkg, null, /* DELETE_ALL_USERS */ 2);
} catch (Exception e) {
Timber.e(e, "could not call deletePackage via reflection");
}
}
/**
* KILL IT!
*
* @param process The name of the application / process to kill
*/
public static void killProcess(Context context, @NonNull String process) {
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
am.killBackgroundProcesses(process);
RootShell.fireAndForget(BusyBox.callBusyBoxApplet("pkill", String.format("-TERM %s;", process)));
}
/**
* Search for a progress and return it via Otto's event bus.
*
* @param process The process name to search for
*/
public static void getProcess(final OnShellOutputListener listener, final String process) {
Utils.getCommandResult(listener, ID_PGREP, BusyBox.callBusyBoxApplet("pgrep", process));
}
/**
* Checks if the given application is running
*
* @param pkg The package name of the application
* @return Whether the app is running
*/
@DebugLog public static boolean isAppRunning(Context context, @NonNull String pkg) {
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final List<ActivityManager.RunningAppProcessInfo> processList = am.getRunningAppProcesses();
if (processList != null) {
for (final ActivityManager.RunningAppProcessInfo procInfo : processList) {
if (pkg.equals(procInfo.processName)) {
return true;
}
}
if (processList.size() <= 1) {
Timber.v("Using fallback to get process list");
final List<ProcessManager.Process> processes = ProcessManager.getRunningApps();
for (final ProcessManager.Process process : processes) {
if (pkg.equals(process.name)) {
return true;
}
}
}
} else {
Timber.e("Could not get list of running processes!");
}
return false;
}
/**
* Checks if a specific service is running.
*
* @param serviceName The name of the service
* @return Whether the service is running or not
*/
public static boolean isServiceRunning(final String serviceName) {
final List<ActivityManager.RunningServiceInfo> services =
((ActivityManager) App.get().getSystemService(Context.ACTIVITY_SERVICE))
.getRunningServices(Integer.MAX_VALUE);
if (services != null) {
for (final ActivityManager.RunningServiceInfo info : services) {
if (info.service != null) {
if (info.service.getClassName() != null && info.service.getClassName()
.equalsIgnoreCase(serviceName)) {
return true;
}
}
}
}
return false;
}
/**
* Converts a size, given as long, to a human readable representation
*
* @param size The size to convert
* @return A human readable data size
*/
public static String convertSize(final long size) {
if (size <= 0) { return "0 B"; }
final String[] units = new String[]{ "B", "KB", "MB", "GB", "TB" };
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.##")
.format(size / Math.pow(1024, digitGroups)) + ' ' + units[digitGroups];
}
/**
* Check if a specific package is installed.
*
* @param packageName The package name
* @return true if package is installed, false otherwise
*/
public static boolean isPackageInstalled(@NonNull final String packageName) {
try {
App.get().getPackageManager().getPackageInfo(packageName, 0);
return true;
} catch (Exception ignored) { }
return false;
}
/**
* Check if Google Play Store is installed
*
* @return true if installed
*/
public static boolean isPlayStoreInstalled() {
return isPackageInstalled("com.android.vending");
}
/**
* Shows the app in Google's Play Store if Play Store is installed
*/
public static boolean showInPlayStore(final String packageName) {
final String url = String.format("market://details?id=%s", packageName);
return AppHelper.viewInBrowser(App.get(), url);
}
public static boolean viewInBrowser(final Context context, final String url) {
final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(i);
return true;
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
return false;
}
public static void startMediaScan(@Nullable View view, @Nullable Context context) {
final String format = "am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://%s;";
final StringBuilder sb = new StringBuilder();
sb.append(String.format(format, IOUtils.get().getPrimarySdCard()));
if (!TextUtils.isEmpty(IOUtils.get().getSecondarySdCard())) {
sb.append(String.format(format, IOUtils.get().getSecondarySdCard()));
}
RootShell.fireAndForget(sb.toString());
if (view != null) {
Snackbar.make(view, R.string.media_scan_triggered, Snackbar.LENGTH_LONG).show();
} else if (context != null) {
Toast.makeText(context, R.string.media_scan_triggered, Toast.LENGTH_LONG).show();
}
}
}