/* * 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.modules.appmanager; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageStats; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.NavUtils; import android.support.v4.content.ContextCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.utils.ColorTemplate; import org.namelessrom.devicecontrol.App; import org.namelessrom.devicecontrol.R; import org.namelessrom.devicecontrol.activities.BaseActivity; import org.namelessrom.devicecontrol.modules.appmanager.permissions.AppSecurityPermissions; import org.namelessrom.devicecontrol.utils.AppHelper; import java.util.ArrayList; import hugo.weaving.DebugLog; import timber.log.Timber; public class AppDetailsActivity extends BaseActivity implements PackageStatsObserver.OnPackageStatsListener, View.OnClickListener { public static final String ARG_FROM_ACTIVITY = "arg_from_activity"; public static final String ARG_PACKAGE_NAME = "arg_package_name"; private static final int DIALOG_TYPE_DISABLE = 0; private static final int DIALOG_TYPE_UNINSTALL = 1; private static final int ANIM_DUR = 1000; private static final Handler mHandler = new Handler(); private final PackageManager mPm = App.get().getPackageManager(); private AppItem mAppItem; private View mAppDetailsContainer; private View mAppDetailsError; private LinearLayout mAppContainer; private AppIconImageView mAppIcon; private TextView mAppLabel; private TextView mAppPackage; private TextView mAppVersion; private Button mForceStop; private Button mUninstall; private PieChart mCacheGraph; private LinearLayout mCacheInfo; @Override protected void onNewIntent(final Intent intent) { super.onNewIntent(intent); setIntent(intent); } @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_app_details); // Setup toolbar final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final Intent i = getIntent(); if (i != null && i.hasExtra(ARG_FROM_ACTIVITY) && getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } final String[] titles = { getString(R.string.app_details) }; final AppPagerAdapter adapter = new AppPagerAdapter(titles); final ViewPager viewPager = (ViewPager) findViewById(R.id.pager); viewPager.setAdapter(adapter); final TabLayout tabHost = (TabLayout) findViewById(R.id.tabHost); // TODO: make it visible once used tabHost.setVisibility(View.GONE); //tabHost.setupWithViewPager(viewPager); mAppDetailsContainer = findViewById(R.id.app_details_container); mAppDetailsError = findViewById(R.id.app_details_error); mAppContainer = (LinearLayout) findViewById(R.id.item_app); // allow to launch app via pressing on the icon mAppIcon = (AppIconImageView) findViewById(R.id.app_icon); mAppIcon.setOnClickListener(this); mAppLabel = (TextView) findViewById(R.id.app_label); mAppPackage = (TextView) findViewById(R.id.app_package); mAppVersion = (TextView) findViewById(R.id.app_version); mCacheGraph = (PieChart) findViewById(R.id.app_cache_graph); mCacheInfo = (LinearLayout) findViewById(R.id.app_cache_info_container); mForceStop = (Button) findViewById(R.id.app_force_stop); ViewCompat.setElevation(mForceStop, 2.0f); mForceStop.setOnClickListener(this); mUninstall = (Button) findViewById(R.id.app_uninstall); ViewCompat.setElevation(mUninstall, 2.0f); mUninstall.setOnClickListener(this); final Button clearCache = (Button) findViewById(R.id.clear_cache); ViewCompat.setElevation(clearCache, 2.0f); clearCache.setOnClickListener(this); final Button clearData = (Button) findViewById(R.id.clear_data); ViewCompat.setElevation(clearData, 2.0f); clearData.setOnClickListener(this); } @Override protected void onResume() { super.onResume(); final String packageName = getTargetPackageName(getIntent()); if (!TextUtils.isEmpty(packageName)) { PackageInfo info = null; try { info = mPm.getPackageInfo(packageName, 0); } catch (Exception ignored) { } if (info != null && info.applicationInfo != null) { mAppItem = new AppItem(info, String.valueOf(info.applicationInfo.loadLabel(mPm))); } } setupCacheGraph(); refreshAppDetails(); } @DebugLog @Nullable private String getTargetPackageName(@Nullable Intent intent) { String packageName = null; Bundle args = null; if (intent != null) { args = intent.getExtras(); packageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; } if (packageName == null) { intent = (args == null) ? getIntent() : (Intent) args.getParcelable("intent"); if (intent != null && intent.getData() != null) { packageName = intent.getData().getSchemeSpecificPart(); } } return packageName; } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu_app_details, menu); if (mAppItem != null) { if (!AppHelper.isPlayStoreInstalled()) { menu.removeItem(R.id.menu_action_play_store); } // prevent disabling Device Control if (App.get().getPackageName().equals(mAppItem.getPackageName())) { menu.removeItem(R.id.menu_app_disable); } final MenuItem disable = menu.findItem(R.id.menu_app_disable); if (disable != null) { disable.setTitle(mAppItem.isEnabled() ? R.string.disable : R.string.enable); } } else { menu.removeItem(R.id.menu_action_play_store); menu.removeItem(R.id.menu_app_disable); } return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { case android.R.id.home: { NavUtils.navigateUpFromSameTask(this); return true; } case R.id.menu_action_refresh: { refreshAppDetails(); return true; } case R.id.menu_action_play_store: { final String pkgName = (mAppItem != null ? mAppItem.getPackageName() : null); if (!TextUtils.isEmpty(pkgName)) { AppHelper.showInPlayStore(pkgName); } return true; } case R.id.menu_app_disable: { disableApp(); return true; } } return super.onOptionsItemSelected(item); } @Override public void onClick(@NonNull View v) { final int id = v.getId(); switch (id) { case R.id.app_force_stop: { forceStopApp(); break; } case R.id.app_uninstall: { uninstallApp(); break; } case R.id.clear_cache: { clearAppCache(); break; } case R.id.clear_data: { clearAppData(); break; } case R.id.app_icon: { final boolean success = mAppItem.launchActivity(this); if (!success) { Snackbar.make(mAppIcon, R.string.could_not_launch_activity, Snackbar.LENGTH_SHORT).show(); } break; } } } private void setupCacheGraph() { mCacheGraph.setDescription(""); mCacheGraph.setHoleRadius(60f); mCacheGraph.setDrawHoleEnabled(true); mCacheGraph.setTransparentCircleRadius(65f); mCacheGraph.setDrawSliceText(false); mCacheGraph.setDrawMarkerViews(false); mCacheGraph.setDrawCenterText(true); mCacheGraph.setTouchEnabled(false); mCacheGraph.setBackgroundResource(R.color.graph_background); mCacheGraph.animateXY(ANIM_DUR, ANIM_DUR); } private void refreshAppDetails() { if (mAppItem == null) { mAppDetailsContainer.setVisibility(View.GONE); mAppDetailsError.setVisibility(View.VISIBLE); invalidateOptionsMenu(); return; } mAppDetailsContainer.setVisibility(View.VISIBLE); mAppDetailsError.setVisibility(View.GONE); mAppIcon.loadImage(mAppItem); mAppLabel.setText(mAppItem.getLabel()); mAppPackage.setText(mAppItem.getPackageName()); mAppContainer.setBackgroundResource(mAppItem.isEnabled() ? android.R.color.transparent : R.color.darker_gray); final int color = mAppItem.isSystemApp() ? ContextCompat.getColor(this, R.color.red_middle) : ContextCompat.getColor(this, R.color.graph_text_color); mAppLabel.setTextColor(color); mAppVersion.setText(mAppItem.getVersion()); setupPermissionsView(); // prevent uninstalling of Device Control mUninstall.setEnabled(!TextUtils.equals(App.get().getPackageName(), mAppItem.getPackageName())); AppHelper.getSize(mPm, this, mAppItem.getPackageName()); refreshAppControls(); invalidateOptionsMenu(); } private void setupPermissionsView() { final LinearLayout permissionsView = (LinearLayout) findViewById(R.id.permissions_section); final boolean supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; if (!supported) { permissionsView.setVisibility(View.GONE); return; } AppSecurityPermissions asp = new AppSecurityPermissions(this, mAppItem.getPackageName()); // Make the security sections header visible LinearLayout securityList = (LinearLayout) findViewById(R.id.security_settings_list); securityList.removeAllViews(); securityList.addView(asp.getPermissionsView()); // If this app is running under a shared user ID with other apps, // update the description to explain this. String[] packages = mPm.getPackagesForUid(mAppItem.getApplicationInfo().uid); if (packages != null && packages.length > 1) { final ArrayList<CharSequence> pnames = new ArrayList<>(); for (final String pkg : packages) { if (mAppItem.getPackageName().equals(pkg)) { continue; } try { ApplicationInfo ainfo = mPm.getApplicationInfo(pkg, 0); pnames.add(ainfo.loadLabel(mPm)); } catch (PackageManager.NameNotFoundException ignored) { } } final int N = pnames.size(); if (N > 0) { final Resources res = getResources(); String appListStr; if (N == 1) { appListStr = pnames.get(0).toString(); } else if (N == 2) { appListStr = res.getString(R.string.join_two_items, pnames.get(0), pnames.get(1)); } else { appListStr = pnames.get(N - 2).toString(); for (int i = N - 3; i >= 0; i--) { appListStr = res.getString(i == 0 ? R.string.join_many_items_first : R.string.join_many_items_middle, pnames.get(i), appListStr); } appListStr = res.getString(R.string.join_many_items_last, appListStr, pnames.get(N - 1)); } final TextView descr = (TextView) findViewById(R.id.security_settings_desc); descr.setText(res.getString(R.string.security_settings_desc_multi, mAppItem.getApplicationInfo().loadLabel(mPm), appListStr)); } } } private void refreshAppControls() { mForceStop.setEnabled(mAppItem.isRunning(this)); } private void forceStopApp() { mAppItem.forceStop(this); Snackbar.make(mAppContainer, R.string.force_stopped_app, Snackbar.LENGTH_SHORT).show(); mHandler.postDelayed(new Runnable() { @Override public void run() { refreshAppControls(); } }, 500); } private void disableApp() { showConfirmationDialog(DIALOG_TYPE_DISABLE); } private void uninstallApp() { showConfirmationDialog(DIALOG_TYPE_UNINSTALL); } private void clearAppCache() { mAppItem.clearCache(this); Snackbar.make(mAppContainer, R.string.cleared_cache, Snackbar.LENGTH_SHORT).show(); mHandler.postDelayed(mClearRunnable, 500); } private void clearAppData() { mAppItem.clearData(this); Snackbar.make(mAppContainer, R.string.cleared_data, Snackbar.LENGTH_SHORT).show(); mHandler.postDelayed(mClearRunnable, 500); } private void updateAppEnabled() { if (mAppItem == null) { return; } runOnUiThread(new Runnable() { @Override public void run() { invalidateOptionsMenu(); final boolean isEnabled = !mAppItem.isEnabled(); mAppItem.setEnabled(isEnabled); if (mAppContainer != null) { mAppContainer.setBackgroundResource( isEnabled ? android.R.color.transparent : R.color.darker_gray); } } }); } @Override public void onPackageStats(final PackageStats packageStats) { if (packageStats == null) { return; } final long totalSize = packageStats.codeSize + packageStats.dataSize + packageStats.externalCodeSize + packageStats.externalDataSize + packageStats.externalMediaSize + packageStats.externalObbSize + packageStats.cacheSize + packageStats.externalCacheSize; if (mCacheInfo != null) { mCacheInfo.removeAllViews(); mCacheInfo.addView(addCacheWidget(R.string.total, AppHelper.convertSize(totalSize))); mCacheInfo.addView(addCacheWidget(R.string.app, AppHelper.convertSize(packageStats.codeSize))); mCacheInfo.addView(addCacheWidget(R.string.ext_app, AppHelper.convertSize(packageStats.externalCodeSize))); mCacheInfo.addView(addCacheWidget(R.string.data, AppHelper.convertSize(packageStats.dataSize))); mCacheInfo.addView(addCacheWidget(R.string.ext_data, AppHelper.convertSize(packageStats.externalDataSize))); mCacheInfo.addView(addCacheWidget(R.string.ext_media, AppHelper.convertSize(packageStats.externalMediaSize))); mCacheInfo.addView(addCacheWidget(R.string.ext_obb, AppHelper.convertSize(packageStats.externalObbSize))); mCacheInfo.addView(addCacheWidget(R.string.cache, AppHelper.convertSize(packageStats.cacheSize))); mCacheInfo.addView(addCacheWidget(R.string.ext_cache, AppHelper.convertSize(packageStats.externalCacheSize))); } if (mCacheGraph != null) { final ArrayList<Entry> sliceList = new ArrayList<>(); final ArrayList<String> textList = new ArrayList<>(); // App --------------------------------------------------------------------------------- textList.add(getString(R.string.app)); sliceList.add(new Entry(packageStats.codeSize + packageStats.externalCodeSize, 0)); // Data -------------------------------------------------------------------------------- textList.add(getString(R.string.data)); sliceList.add(new Entry(packageStats.dataSize + packageStats.externalDataSize, 1)); // External ------------------------------------------------------------------------ textList.add(getString(R.string.ext)); sliceList.add(new Entry(packageStats.externalMediaSize + packageStats.externalObbSize, 2)); // Cache ------------------------------------------------------------------------------- textList.add(getString(R.string.cache)); sliceList.add(new Entry(packageStats.cacheSize + packageStats.externalCacheSize, 3)); final PieDataSet dataSet = new PieDataSet(sliceList, getString(R.string.app_size)); // no space to work around a bug dataSet.setSliceSpace(0f); dataSet.setColors(ColorTemplate.createColors(ColorTemplate.VORDIPLOM_COLORS)); final PieData data = new PieData(textList, dataSet); data.getDataSet().setDrawValues(false); mCacheGraph.setData(data); mCacheGraph.highlightValues(null); mCacheGraph.setCenterText(String.format("%s\n%s", getString(R.string.total), AppHelper.convertSize(totalSize))); // setup legend final Legend l = mCacheGraph.getLegend(); l.setPosition(Legend.LegendPosition.BELOW_CHART_CENTER); l.setXEntrySpace(7f); l.setYEntrySpace(5f); final int color = ContextCompat.getColor(this, R.color.graph_text_color); l.setTextColor(color); // we are ready mCacheGraph.animateXY(1000, 1000); } } private View addCacheWidget(final int txtId, final String text) { final View v = getLayoutInflater().inflate(R.layout.widget_app_cache, mCacheInfo, false); final TextView tvLeft = (TextView) v.findViewById(R.id.widget_app_cache_left); tvLeft.setText(txtId); final TextView tvRight = (TextView) v.findViewById(R.id.widget_app_cache_right); tvRight.setText(text); return v; } private final Runnable mClearRunnable = new Runnable() { @Override public void run() { try { AppHelper.getSize(mPm, AppDetailsActivity.this, mAppItem.getPackageName()); } catch (Exception e) { Timber.e(e, "AppHelper.getSize()"); } } }; private void showConfirmationDialog(final int type) { if (mAppItem == null) { return; } final AlertDialog.Builder builder = new AlertDialog.Builder(this); final String message; final int positiveButton; switch (type) { case DIALOG_TYPE_DISABLE: { message = getString(mAppItem.isEnabled() ? R.string.disable_msg : R.string.enable_msg, mAppItem.getLabel()); positiveButton = mAppItem.isEnabled() ? R.string.disable : R.string.enable; break; } case DIALOG_TYPE_UNINSTALL: { if (mAppItem.isSystemApp()) { message = String.format("%s\n\n%s", getString(R.string.uninstall_msg, mAppItem.getLabel()), getString(R.string.uninstall_msg_system_app)); } else { message = getString(R.string.uninstall_msg, mAppItem.getLabel()); } positiveButton = android.R.string.yes; break; } default: { return; } } builder.setMessage(message) .setPositiveButton(positiveButton, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (type) { case DIALOG_TYPE_DISABLE: { mAppItem.disableOrEnable( new AppItem.DisableEnableListener() { @Override public void OnDisabledOrEnabled() { updateAppEnabled(); } }); break; } case DIALOG_TYPE_UNINSTALL: { mAppItem.uninstall(AppDetailsActivity.this, new AppItem.UninstallListener() { @Override public void OnUninstallComplete() { mAppItem = null; AppDetailsActivity.this.finish(); } }); break; } default: { dialog.dismiss(); break; } } } } ) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.show(); } class AppPagerAdapter extends PagerAdapter { private final String[] mTitles; public AppPagerAdapter(final String[] titles) { mTitles = titles; } @Override public String getPageTitle(final int position) { return mTitles[position]; } @Override public Object instantiateItem(ViewGroup container, int position) { final int resId; switch (position) { default: case 0: { resId = R.id.page_app_details; break; } // TODO: make visible once done //case 1: { // resId = R.id.page_app_details; // break; //} } return findViewById(resId); } @Override public int getCount() { return mTitles.length; } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } } }