package com.zte.appopscontrol; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import com.zte.appopscontrol.AppOpsState2.OpsTemplate; import com.zte.appopscontrol.AppOpsUtils.AppInfo; import android.app.Activity; import android.app.AppOpsManager; import android.app.Fragment; import android.app.ListFragment; import android.app.LoaderManager; import android.content.AsyncTaskLoader; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.Loader; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SectionIndexer; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; /** * Demonstration of the implementation of a custom Loader. */ public class AppFragment extends Fragment implements OnItemClickListener, LoaderManager.LoaderCallbacks<List<AppInfo>> { // This is the Adapter being used to display the list's data. private AppListAdapter mAdapter; private String mCurrentPkgName; private ListView mAppsList; private LayoutInflater mInflater; private static TextView mHeaderView; private View mContentView; private LinearLayout mProgressContainer; TextView mEmptyView; // If non-null, this is the current filter the user has provided. private String mCurFilter; private static SharedPreferences mPreferences; private PackageManager mPm; private static Activity mActivity; private static AppOpsManager mAppOps; private static AppOpsState2 mState; public AppFragment() { } /** * Perform alphabetical comparison of application entry objects. */ public static final Comparator<AppInfo> ALPHA_COMPARATOR = new Comparator<AppInfo>() { private final Collator sCollator = Collator.getInstance(); @Override public int compare(AppInfo object1, AppInfo object2) { return sCollator.compare(object1.getLabel(), object2.getLabel()); } }; /** * Helper for determining if the configuration has changed in an interesting * way so we need to rebuild the app list. */ public static class InterestingConfigChanges { final Configuration mLastConfiguration = new Configuration(); int mLastDensity; boolean applyNewConfig(Resources res) { int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { mLastDensity = res.getDisplayMetrics().densityDpi; return true; } return false; } } /** * Helper class to look for interesting changes to the installed apps * so that the loader can be updated. */ public static class PackageIntentReceiver extends BroadcastReceiver { final AppListLoader mLoader; public PackageIntentReceiver(AppListLoader loader) { mLoader = loader; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mLoader.getContext().registerReceiver(this, filter); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mLoader.getContext().registerReceiver(this, sdFilter); // register for refresh ui event (reload sys app or not) IntentFilter refreshFilter = new IntentFilter(); refreshFilter.addAction(AppOpsControlActivity.ACTION_APPOPS_REFRESH_UI); mLoader.getContext().registerReceiver(this, refreshFilter); } @Override public void onReceive(Context context, Intent intent) { // Tell the loader about the change. mLoader.onContentChanged(); } } public static int getPermCounts(OpsTemplate tpl, int uid, String packageName) { final PackageManager mPm = mActivity.getPackageManager(); //get ops pkgs List<AppOpsManager.PackageOps> pkgs = new ArrayList<AppOpsManager.PackageOps>(); if (packageName != null) { pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops); } //if has pkg ops, it means we have this tpl's permission if( pkgs != null ) return 1; //if no pkg, then we need to check the AnroidManifest.xml using PackageManager PackageInfo appInfo = new PackageInfo(); if (packageName != null) { try { appInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (NameNotFoundException e) { } } for (int i=0; i<tpl.ops.length; i++) { if (tpl.showPerms[i]) { String perm = AppOpsManager.opToPermission(tpl.ops[i]); if (perm != null) { if (appInfo.requestedPermissions != null) { for (int j=0; j<appInfo.requestedPermissions.length; j++) { if (perm.equals(appInfo.requestedPermissions[j])) { return 1; // found permissions in this tpl } } } } } } return 0; } /** * A custom Loader that loads all of the installed applications. */ public static class AppListLoader extends AsyncTaskLoader<List<AppInfo>> { final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); private PackageManager mPm; List<AppInfo> mApps; PackageIntentReceiver mPackageObserver; public AppListLoader(Context context) { super(context); // Retrieve the package manager for later use; note we don't // use 'context' directly but instead the save global application // context returned by getContext(). mPm = getContext().getPackageManager(); } /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */ @Override public List<AppInfo> loadInBackground() { // Retrieve all known applications. List<PackageInfo> packages = mPm.getInstalledPackages( PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); if (packages == null) { packages = new ArrayList<PackageInfo>(); } boolean showSystemApps = shouldShowSystemApps(); Signature platformCert; try { PackageInfo sysInfo = mPm.getPackageInfo("android", PackageManager.GET_SIGNATURES); platformCert = sysInfo.signatures[0]; } catch (PackageManager.NameNotFoundException e) { platformCert = null; } final Context context = getContext(); int pkgPermCounts = 0; long time0, time1; time0 = System.currentTimeMillis(); // Create corresponding array of entries and load their labels. List<AppInfo> entries = new ArrayList<AppInfo>(); for (PackageInfo info : packages) { final ApplicationInfo appInfo = info.applicationInfo; pkgPermCounts = 0; // hide apps signed with the platform certificate to avoid the user // shooting himself in the foot if (platformCert != null && info.signatures != null && platformCert.equals(info.signatures[0])) { continue; } // skip all system apps if they shall not be included if (!showSystemApps && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { continue; } for(int index = 0; index < AppOpsState2.ALL_TEMPLATES.length; index ++){ final AppOpsState2.OpsTemplate tpl = AppOpsState2.ALL_TEMPLATES[index]; // List<AppOpsState2.AppOpEntry> appOpEntries = mState.buildState(tpl, // appInfo.uid, info.packageName); // testCounts += appOpEntries.size(); pkgPermCounts += getPermCounts(tpl, appInfo.uid, info.packageName); } //temp solution: not display the zero perm apps .... if(pkgPermCounts == 0) continue; AppInfo entry = new AppInfo(getContext(), appInfo,pkgPermCounts); entry.loadLabel(context); entries.add(entry); } time1 = System.currentTimeMillis(); //Toast.makeText(mActivity, String.valueOf(time1-time0), Toast.LENGTH_SHORT).show(); Log.e("zteappops","delta time: " + (time1-time0)); // Sort the list. Collections.sort(entries, ALPHA_COMPARATOR); // Done! return entries; } /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */ @Override public void deliverResult(List<AppInfo> apps) { if (isReset()) { // An async query came in while the loader is stopped. We // don't need the result. if (apps != null) { onReleaseResources(apps); } } List<AppInfo> oldApps = apps; mApps = apps; if (isStarted()) { // If the Loader is currently started, we can immediately // deliver its results. super.deliverResult(apps); } // At this point we can release the resources associated with // 'oldApps' if needed; now that the new result is delivered we // know that it is no longer in use. if (oldApps != null) { onReleaseResources(oldApps); } } /** * Handles a request to start the Loader. */ @Override protected void onStartLoading() { // We don't monitor changed when loading is stopped, so need // to always reload at this point. onContentChanged(); if (mApps != null) { // If we currently have a result available, deliver it // immediately. deliverResult(mApps); } // Start watching for changes in the app data. if (mPackageObserver == null) { mPackageObserver = new PackageIntentReceiver(this); } // Has something interesting in the configuration changed since we // last built the app list? boolean configChange = mLastConfig.applyNewConfig(getContext().getResources()); if (takeContentChanged() || mApps == null || configChange) { // If the data has changed since the last time it was loaded // or is not currently available, start a load. forceLoad(); } } /** * Handles a request to stop the Loader. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to cancel a load. */ @Override public void onCanceled(List<AppInfo> apps) { super.onCanceled(apps); // At this point we can release the resources associated with 'apps' // if needed. onReleaseResources(apps); } /** * Handles a request to completely reset the Loader. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'apps' // if needed. if (mApps != null) { onReleaseResources(mApps); mApps = null; } // Stop monitoring for changes. if (mPackageObserver != null) { getContext().unregisterReceiver(mPackageObserver); mPackageObserver = null; } } /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */ protected void onReleaseResources(List<AppInfo> apps) { // For a simple List<> there is nothing to do. For something // like a Cursor, we would close it here. } } public static class AppListAdapter extends BaseAdapter implements SectionIndexer { private final Resources mResources; private final LayoutInflater mInflater; private String[] mSections; private int[] mPositions; List<AppInfo> mList; public AppListAdapter(Context context) { mResources = context.getResources(); mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void setData(List<AppInfo> data,List<String> sections, List<Integer> positions) { mList = data; mSections = sections.toArray(new String[sections.size()]); mPositions = new int[positions.size()]; for (int i = 0; i < positions.size(); i++) { mPositions[i] = positions.get(i); } notifyDataSetChanged(); } @Override public int getCount() { return mList != null ? mList.size() : 0; } @Override public AppInfo getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } public int getSysAppCount() { int counts = getCount(); int sysCounts = 0; if ( counts <= 0 ) return 0; for(int i=0 ; i<counts; i++) { if( (mList.get(i).getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) != 0) sysCounts++; } return sysCounts; } /** * Populate new items in the list. */ @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if( mHeaderView != null) { int sysAppCount = getSysAppCount(); String sysCountUnit = sysAppCount > 1 ? mActivity.getString(R.string.app_count_unit_plural) : mActivity.getString(R.string.app_count_unit_single); String userCountUnit = (getCount()-sysAppCount) > 1 ? mActivity.getString(R.string.app_count_unit_plural) : mActivity.getString(R.string.app_count_unit_single); String appCountStr = mActivity.getString(R.string.app_system) + " " +sysCountUnit +": " + String.valueOf(sysAppCount) + " " + mActivity.getString(R.string.app_user) + " " + userCountUnit + ": " + String.valueOf(getCount()-sysAppCount); mHeaderView.setText(appCountStr); } if (convertView == null) { view = mInflater.inflate(R.layout.app_list_item, parent, false); } else { view = convertView; } AppInfo item = getItem(position); ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable( item.getIcon()); ((TextView)view.findViewById(R.id.app_name)).setText(item.getLabel()); String strPerms = String.valueOf(item.getPermCounts()) + " " + mActivity.getString(R.string.perm_count_unit_plural); ((TextView)view.findViewById(R.id.app_perm_details)).setText(strPerms); return view; } @Override public int getPositionForSection(int section) { if (section < 0 || section >= mSections.length) { return -1; } return mPositions[section]; } @Override public int getSectionForPosition(int position) { if (position < 0 || position >= getCount()) { return -1; } int index = Arrays.binarySearch(mPositions, position); /* * Consider this example: section positions are 0, 3, 5; the supplied * position is 4. The section corresponding to position 4 starts at * position 3, so the expected return value is 1. Binary search will not * find 4 in the array and thus will return -insertPosition-1, i.e. -3. * To get from that number to the expected value of 1 we need to negate * and subtract 2. */ return index >= 0 ? index : -index - 2; } @Override public Object[] getSections() { return mSections; } } private static boolean shouldShowSystemApps() { return mPreferences.getBoolean("show_system_apps", false); } private void setListShown(boolean shown) { if (shown) { mHeaderView.setVisibility(View.VISIBLE); mAppsList.setVisibility(View.VISIBLE); mProgressContainer.setVisibility(View.GONE); } else { mEmptyView.setVisibility(View.GONE); mHeaderView.setVisibility(View.GONE); mAppsList.setVisibility(View.GONE); mProgressContainer.setVisibility(View.VISIBLE); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mState = new AppOpsState2(getActivity()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mContentView = inflater.inflate(R.layout.app_list_fragment, null); mProgressContainer = (LinearLayout) mContentView.findViewById(R.id.progressContainer); mHeaderView = (TextView) mContentView.findViewById(R.id.header); mEmptyView = (TextView)mContentView.findViewById(android.R.id.empty); mEmptyView.setText("No Apps!"); ListView lv = (ListView) mContentView.findViewById(android.R.id.list); if (mEmptyView != null) { lv.setEmptyView(mEmptyView); } lv.setOnItemClickListener(this); lv.setSaveEnabled(true); lv.setItemsCanFocus(true); lv.setTextFilterEnabled(true); lv.setFastScrollEnabled(true); mAppsList = lv; return mContentView; } @Override public void onResume() { super.onResume(); //load(); onRefreshUi(); } public void onRefreshUi() { this.getLoaderManager().getLoader(0).onContentChanged(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mActivity = getActivity(); mPm = mActivity.getPackageManager(); mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE); //get shared preference mPreferences = mActivity.getSharedPreferences(AppOpsControlActivity.APP_OPS_SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE); // Create an empty adapter we will use to display the loaded data. mAdapter = new AppListAdapter(getActivity()); mAppsList.setAdapter(mAdapter); // Start out with a progress indicator. setListShown(false); // Prepare the loader. getLoaderManager().initLoader(0, null, this); } // utility method used to start sub activity private void startApplicationDetailsActivity() { // start new fragment to display extended information Bundle args = new Bundle(); args.putString(AppOpsDetailsActivity.ARG_PACKAGE_NAME, mCurrentPkgName); Intent intent = new Intent(); intent.putExtras(args); intent.setClass(getActivity(), AppOpsDetailsActivity.class); startActivity(intent); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { AppInfo entry = mAdapter.getItem(position); if (entry != null) { mCurrentPkgName = entry.getApplicationInfo().packageName; startApplicationDetailsActivity(); } } @Override public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader with no arguments, so it is simple. return new AppListLoader(getActivity()); } // helper function to set data private void setApdaterData(List<AppInfo> data){ List<AppInfo> apps = data; String lastSectionIndex = null; ArrayList<String> sections = new ArrayList<String>(); ArrayList<Integer> positions = new ArrayList<Integer>(); int count = apps==null ? 0 : apps.size(), offset = 0; for (int i = 0; i < count; i++) { AppInfo app = apps.get(i); String sectionIndex; if (app.getLabel().isEmpty()) { sectionIndex = ""; } else { sectionIndex = app.getLabel().substring(0, 1).toUpperCase(); } if (lastSectionIndex == null) { lastSectionIndex = sectionIndex; } if (!TextUtils.equals(sectionIndex, lastSectionIndex)) { sections.add(sectionIndex); positions.add(offset); lastSectionIndex = sectionIndex; } offset++; } mAdapter.setData(data,sections,positions); } @Override public void onLoadFinished(Loader<List<AppInfo>> loader, List<AppInfo> data) { // Set the new data in the adapter. //mAdapter.setData(data); setApdaterData(data); setListShown(true); } @Override public void onLoaderReset(Loader<List<AppInfo>> loader) { // Clear the data in the adapter. //mAdapter.setData(null); setApdaterData(null); } }