package com.majeur.applicationsinfo;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ListFragment;
import android.app.LoaderManager;
import android.app.ProgressDialog;
import android.content.Loader;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.Spannable;
import android.text.format.Formatter;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.SectionIndexer;
import android.widget.TextView;
import com.majeur.applicationsinfo.utils.Utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.Collator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class MainListFragment extends ListFragment implements AdapterView.OnItemClickListener, SearchView.OnQueryTextListener,
LoaderManager.LoaderCallbacks<List<ApplicationItem>> {
private static Collator sCollator = Collator.getInstance();
private static final int[] sSortMenuItemIdsMap = {R.id.action_sort_name,
R.id.action_sort_pkg, R.id.action_sort_domain,
R.id.action_sort_installation, R.id.action_sort_size};
private static final int SORT_NAME = 0;
private static final int SORT_PKG = 1;
private static final int SORT_DOMAIN = 2;
private static final int SORT_INSTALLATION = 3;
private static final int SORT_SIZE = 4;
private static final String INSTANCE_STATE_SORT_BY = "sort_by";
private Adapter mAdapter;
private List<ApplicationItem> mItemList = new ArrayList<>();
private int mItemSizeRetrievedCount;
private ProgressDialog mProgressDialog;
private MainCallbacks mCallbacks;
private Activity mActivity;
private int mSortBy = SORT_NAME;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mProgressDialog = new ProgressDialog(mActivity);
mProgressDialog.setTitle(R.string.loading_apps);
mProgressDialog.setCancelable(false);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setDisplayShowCustomEnabled(true);
SearchView searchView = new SearchView(actionBar.getThemedContext());
searchView.setOnQueryTextListener(this);
ActionBar.LayoutParams layoutParams = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
actionBar.setCustomView(searchView, layoutParams);
if (savedInstanceState != null) {
int sortBy = savedInstanceState.getInt(INSTANCE_STATE_SORT_BY, -1);
if (sortBy != -1)
setSortBy(sortBy);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(INSTANCE_STATE_SORT_BY, mSortBy);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setOnItemClickListener(this);
getListView().setFastScrollEnabled(true);
mAdapter = new Adapter(mActivity);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
@Override
public Loader<List<ApplicationItem>> onCreateLoader(int i, Bundle bundle) {
mProgressDialog.show();
return new MainLoader(mActivity);
}
@Override
public void onLoadFinished(Loader<List<ApplicationItem>> loader, List<ApplicationItem> applicationItems) {
mItemList = applicationItems;
sortApplicationList();
mAdapter.setDefaultList(mItemList);
startRetrievingPackagesSize();
mProgressDialog.dismiss();
}
@Override
public void onLoaderReset(Loader<List<ApplicationItem>> loader) {
mItemList = null;
mAdapter.setDefaultList(null);
mProgressDialog.dismiss();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (MainCallbacks) activity;
mActivity = activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
mActivity = null;
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mCallbacks != null)
mCallbacks.onItemSelected(mAdapter.getItem(i).applicationInfo.packageName);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_main_list, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(sSortMenuItemIdsMap[mSortBy]).setChecked(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int id = item.getItemId();
switch (id) {
case R.id.action_refresh:
getLoaderManager().restartLoader(0, null, this);
return true;
case R.id.action_sort_name:
setSortBy(SORT_NAME);
item.setChecked(true);
return true;
case R.id.action_sort_pkg:
setSortBy(SORT_PKG);
item.setChecked(true);
return true;
case R.id.action_sort_domain:
setSortBy(SORT_DOMAIN);
item.setChecked(true);
return true;
case R.id.action_sort_installation:
setSortBy(SORT_INSTALLATION);
item.setChecked(true);
return true;
case R.id.action_sort_size:
setSortBy(SORT_SIZE);
item.setChecked(true);
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Sort main list if provided value is valid.
*
* @param sort Must be one of SORT_*
*/
private void setSortBy(int sort) {
mSortBy = sort;
sortApplicationList();
if (mAdapter != null)
mAdapter.notifyDataSetChanged();
if (getListView() != null)
checkFastScroll();
}
private void checkFastScroll() {
getListView().setFastScrollEnabled(mSortBy == SORT_NAME);
}
public void sortApplicationList() {
Collections.sort(mItemList, new Comparator<ApplicationItem>() {
@Override
public int compare(ApplicationItem item1, ApplicationItem item2) {
switch (mSortBy) {
case SORT_NAME:
return sCollator.compare(item1.label, item2.label);
case SORT_PKG:
return item1.applicationInfo.packageName.compareTo(item2.applicationInfo.packageName);
case SORT_DOMAIN:
boolean isSystem1 = (item1.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean isSystem2 = (item2.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
return Utils.compareBooleans(isSystem1, isSystem2);
case SORT_INSTALLATION:
//Sort in decreasing order
return -item1.date.compareTo(item2.date);
case SORT_SIZE:
return -item1.size.compareTo(item2.size);
default:
return 0;
}
}
});
}
@Override
public boolean onQueryTextChange(String s) {
mAdapter.getFilter().filter(s);
return true;
}
@Override
public boolean onQueryTextSubmit(String s) {
return false;
}
private void startRetrievingPackagesSize() {
for (ApplicationItem item : mItemList)
getItemSize(item);
}
private void getItemSize(final ApplicationItem item) {
try {
Method getPackageSizeInfo = PackageManager.class.getMethod(
"getPackageSizeInfo", String.class, IPackageStatsObserver.class);
getPackageSizeInfo.invoke(mActivity.getPackageManager(), item.applicationInfo.packageName, new IPackageStatsObserver.Stub() {
@Override
public void onGetStatsCompleted(final PackageStats pStats, final boolean succeeded)
throws RemoteException {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (succeeded)
item.size = pStats.codeSize + pStats.cacheSize + pStats.dataSize
+ pStats.externalCodeSize + pStats.externalCacheSize + pStats.externalDataSize
+ pStats.externalMediaSize + pStats.externalObbSize;
else
item.size = -1L;
incrementItemSizeRetrievedCount();
}
});
}
});
} catch (NoSuchMethodException e) {
e.printStackTrace();
incrementItemSizeRetrievedCount();
} catch (IllegalAccessException e) {
e.printStackTrace();
incrementItemSizeRetrievedCount();
} catch (InvocationTargetException e) {
e.printStackTrace();
incrementItemSizeRetrievedCount();
}
}
private void incrementItemSizeRetrievedCount() {
mItemSizeRetrievedCount++;
if (mItemSizeRetrievedCount == mItemList.size())
mAdapter.notifyDataSetChanged();
}
static class Adapter extends BaseAdapter implements SectionIndexer, Filterable {
static final String sections = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static final DateFormat sSimpleDateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
static final Spannable.Factory sSpannableFactory = Spannable.Factory.getInstance();
static class ViewHolder {
ImageView icon;
TextView label;
TextView packageName;
TextView version;
TextView isSystemApp;
TextView date;
TextView size;
IconAsyncTask iconLoader;
}
private Activity mActivity;
private LayoutInflater mLayoutInflater;
private PackageManager mPackageManager;
private Filter mFilter;
private String mConstraint;
private List<ApplicationItem> mDefaultList;
private List<ApplicationItem> mAdapterList;
private int mColorGrey1;
private int mColorGrey2;
Adapter(Activity activity) {
mActivity = activity;
mLayoutInflater = activity.getLayoutInflater();
mPackageManager = activity.getPackageManager();
mColorGrey1 = activity.getResources().getColor(R.color.grey_1);
mColorGrey2 = activity.getResources().getColor(R.color.grey_2);
}
void setDefaultList(List<ApplicationItem> list) {
mDefaultList = list;
mAdapterList = list;
notifyDataSetChanged();
}
@Override
public Filter getFilter() {
if (mFilter == null)
mFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
String constraint = charSequence.toString().toLowerCase();
mConstraint = constraint;
FilterResults filterResults = new FilterResults();
if (constraint.length() == 0) {
filterResults.count = 0;
filterResults.values = null;
return filterResults;
}
List<ApplicationItem> list = new ArrayList<>(mDefaultList.size());
for (ApplicationItem item : mDefaultList) {
if (item.label.toLowerCase().contains(constraint) ||
item.applicationInfo.packageName.contains(constraint))
list.add(item);
}
filterResults.count = list.size();
filterResults.values = list;
return filterResults;
}
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
if (filterResults.values == null)
mAdapterList = mDefaultList;
else
mAdapterList = (List<ApplicationItem>) filterResults.values;
notifyDataSetChanged();
}
};
return mFilter;
}
@Override
public int getCount() {
return mAdapterList == null ? 0 : mAdapterList.size();
}
@Override
public ApplicationItem getItem(int i) {
return mAdapterList.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
view = mLayoutInflater.inflate(R.layout.main_list_item, viewGroup, false);
holder = new ViewHolder();
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.label = (TextView) view.findViewById(R.id.label);
holder.packageName = (TextView) view.findViewById(R.id.packageName);
holder.version = (TextView) view.findViewById(R.id.version);
holder.isSystemApp = (TextView) view.findViewById(R.id.isSystem);
holder.date = (TextView) view.findViewById(R.id.date);
holder.size = (TextView) view.findViewById(R.id.size);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
holder.iconLoader.cancel(true);
}
view.setBackgroundColor(i % 2 == 0 ? mColorGrey2 : mColorGrey1);
ApplicationItem item = mAdapterList.get(i);
ApplicationInfo info = item.applicationInfo;
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName, 0);
holder.version.setText(packageInfo.versionName);
Date date = new Date(packageInfo.firstInstallTime);
holder.date.setText(sSimpleDateFormat.format(date));
} catch (PackageManager.NameNotFoundException e) {
//Do nothing
}
holder.iconLoader = new IconAsyncTask(holder.icon, info);
holder.iconLoader.execute();
if (mConstraint != null && item.label.toLowerCase().contains(mConstraint))
holder.label.setText(getHighlightedText(item.label));
else
holder.label.setText(item.label);
if (mConstraint != null && info.packageName.contains(mConstraint))
holder.packageName.setText(getHighlightedText(info.packageName));
else
holder.packageName.setText(info.packageName);
boolean isSystemApp = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
holder.isSystemApp.setText(isSystemApp ? mActivity.getString(R.string.system) : mActivity.getString(R.string.user));
if (item.size != -1L)
holder.size.setText(Formatter.formatFileSize(mActivity, item.size));
return view;
}
Spannable getHighlightedText(String s) {
Spannable spannable = sSpannableFactory.newSpannable(s);
int start = s.toLowerCase().indexOf(mConstraint);
int end = start + mConstraint.length();
spannable.setSpan(new BackgroundColorSpan(0xFFB7B7B7), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
@Override
public int getPositionForSection(int section) {
for (int i = 0; i < this.getCount(); i++) {
String item = mAdapterList.get(i).label;
if (item.length() > 0) {
if (item.charAt(0) == sections.charAt(section))
return i;
}
}
return 0;
}
@Override
public int getSectionForPosition(int i) {
return 0;
}
@Override
public Object[] getSections() {
String[] sectionsArr = new String[sections.length()];
for (int i = 0; i < sections.length(); i++)
sectionsArr[i] = "" + sections.charAt(i);
return sectionsArr;
}
class IconAsyncTask extends AsyncTask<Void, Integer, Drawable> {
ImageView imageView;
ApplicationInfo info;
IconAsyncTask(ImageView imageView, ApplicationInfo info) {
this.imageView = imageView;
this.info = info;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
imageView.setVisibility(View.INVISIBLE);
}
@Override
protected Drawable doInBackground(Void... voids) {
if (!isCancelled())
return info.loadIcon(mPackageManager);
return null;
}
@Override
protected void onPostExecute(Drawable drawable) {
super.onPostExecute(drawable);
imageView.setImageDrawable(drawable);
imageView.setVisibility(View.VISIBLE);
}
}
}
}