X-Git-Url: https://git.mdrn.pl/mobilnebezpieczenstwo.git/blobdiff_plain/4b08fd3d054a0b4872d60adc7627bfdef069c39b..be45943134f44b96728f516e91a66149e51443c0:/app/src/main/java/com/samsung/srpol/loader/AppListLoader.java diff --git a/app/src/main/java/com/samsung/srpol/loader/AppListLoader.java b/app/src/main/java/com/samsung/srpol/loader/AppListLoader.java new file mode 100644 index 0000000..3dfdb0f --- /dev/null +++ b/app/src/main/java/com/samsung/srpol/loader/AppListLoader.java @@ -0,0 +1,627 @@ +/* + Copyright (C) 2014 Samsung Electronics Polska Sp. z o.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU AFFERO General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + You may obtain a copy of the License at + + http://www.gnu.org/licenses/agpl-3.0.txt + + 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 . +*/ + +package com.samsung.srpol.loader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.content.res.XmlResourceParser; +import android.support.v4.content.AsyncTaskLoader; + +import com.samsung.srpol.R; +import com.samsung.srpol.data.Category; +import com.samsung.srpol.data.Subcategory; + +/** + * A custom Loader that loads all of the installed applications. + */ +public class AppListLoader extends AsyncTaskLoader> { + private static final String SYSTEM_UI = "com.android.systemui"; + private static final String PHONE = "com.android.phone"; + + public static final String PREF_INCLUDE_SYSTEM_APPS = "include_system_apps"; + + private final PackageManager mPm; + + private static List mAppDetailsList = null; + private static List mCategories; + private static ArrayList mSubcategories; + private static HashMap mAllPermissionsHash; + + private PackageIntentReceiver mPackageObserver; + private boolean mWasDataReloaded = true; + private static OnAppRemoveListener mChangeListener = null; + + public AppListLoader(Context context) { + super(context); + + if (mCategories == null || mSubcategories == null + || mAllPermissionsHash == null) + initCategories(context); + + mPm = getContext().getPackageManager(); + } + + private void initCategories(Context context) { + mCategories = new ArrayList(); + mSubcategories = new ArrayList(); + Subcategory.resetGenerator(); + + // Reading "data send" subcategory as it is common + try { + XmlResourceParser parser = context.getResources().getXml( + R.xml.data_send_subcategory); + int eventType = 0; + while (eventType != XmlResourceParser.END_DOCUMENT + && eventType != XmlResourceParser.START_TAG) + eventType = parser.next(); + if (eventType != XmlResourceParser.END_DOCUMENT) { + Subcategory subcategory = readSubCategory(parser); + mSubcategories.add(subcategory); + } + } catch (XmlPullParserException ex) { + ex.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + // Reading all categories and subcategories from xml + try { + XmlResourceParser parser = context.getResources().getXml( + R.xml.categories); + int eventType = 0; + while (eventType != XmlResourceParser.END_DOCUMENT + && eventType != XmlResourceParser.START_TAG) + eventType = parser.next(); + if (eventType != XmlResourceParser.END_DOCUMENT) { + parser.require(XmlResourceParser.START_TAG, null, "container"); + while (parser.next() != XmlResourceParser.END_TAG) { + if (parser.getEventType() != XmlResourceParser.START_TAG) + continue; + if (!parser.getName().equals("category")) + continue; + Category cat = readCategory(parser); + mCategories.add(cat); + mSubcategories.addAll(cat.getSubCategories()); + } + } + } catch (XmlPullParserException ex) { + ex.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + mAllPermissionsHash = new HashMap(); + for (Subcategory subcat : mSubcategories) { + for (String perm : subcat.getPermissions()) { + Integer value = mAllPermissionsHash.get(perm); + if (value == null) { + mAllPermissionsHash.put(perm, Integer.valueOf(subcat.getId())); + } else { + value |= subcat.getId(); + mAllPermissionsHash.put(perm, value); + } + } + } + } + + private Category readCategory(XmlResourceParser parser) + throws XmlPullParserException, IOException { + parser.require(XmlResourceParser.START_TAG, null, "category"); + String title = null, header = null, description = null, shortDescription = null, link = null; + int icon = 0; + boolean dataSend = false; + ArrayList subCategories = new ArrayList(); + + while (parser.next() != XmlResourceParser.END_TAG) { + if (parser.getEventType() != XmlResourceParser.START_TAG) + continue; + String name = parser.getName(); + if (name.equals("title")) { + title = readTextElement(parser, name); + } else if (name.equals("header")) { + header = readTextElement(parser, name); + } else if (name.equals("short_description")) { + shortDescription = readTextElement(parser, name); + } else if (name.equals("description")) { + description = readTextElement(parser, name); + } else if (name.equals("icon")) { + icon = getContext().getResources().getIdentifier( + readTextElement(parser, name), "drawable", + getContext().getPackageName()); + } else if (name.equals("link")) { + link = readTextElement(parser, name); + } else if (name.equals("dataSend")) { + dataSend = Boolean.parseBoolean(readTextElement(parser, name)); + } else if (name.equals("subcategories")) { + while (parser.next() != XmlResourceParser.END_TAG) { + subCategories.add(readSubCategory(parser)); + } + } else + skip(parser); + } + return new Category(getContext(), title, header, shortDescription, + description, icon, link, dataSend, subCategories); + } + + private Subcategory readSubCategory(XmlResourceParser parser) + throws XmlPullParserException, IOException { + parser.require(XmlResourceParser.START_TAG, null, "subcategory"); + String header = null, description = null, icon = null; + ArrayList permissions = new ArrayList(); + + while (parser.next() != XmlResourceParser.END_TAG) { + if (parser.getEventType() != XmlResourceParser.START_TAG) + continue; + String name = parser.getName(); + if (name.equals("header")) { + header = readTextElement(parser, name); + } else if (name.equals("description")) { + description = readTextElement(parser, name); + } else if (name.equals("icon")) { + icon = readTextElement(parser, name); + } else if (name.equals("permissions")) { + while (parser.next() != XmlResourceParser.END_TAG) { + permissions.add(readTextElement(parser, "item")); + } + } else + skip(parser); + } + + return new Subcategory(getContext(), header, description, icon, + permissions); + } + + private String readTextElement(XmlResourceParser parser, String element) + throws XmlPullParserException, IOException { + parser.require(XmlResourceParser.START_TAG, null, element); + String title = readText(parser); + parser.require(XmlResourceParser.END_TAG, null, element); + return title; + } + + private String readText(XmlResourceParser parser) + throws XmlPullParserException, IOException { + String result = ""; + if (parser.next() == XmlResourceParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + private void skip(XmlResourceParser parser) throws XmlPullParserException, + IOException { + if (parser.getEventType() != XmlResourceParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlResourceParser.END_TAG: + depth--; + break; + case XmlResourceParser.START_TAG: + depth++; + break; + } + } + } + + /** + * Get Signature keys of given package + * + * @param packageName + * package name to retrieve signature keys + * @return Signature[] containing given package name signature keys + */ + private Signature[] getSignature(String packageName) { + + try { + PackageInfo packageInfo = getPm().getPackageInfo(packageName, + PackageManager.GET_SIGNATURES); + Signature[] sigs = packageInfo.signatures; + return sigs; + } catch (NameNotFoundException e) { + return null; + } + + } + + /** + * Get Signature keys for given PackageInfo + * + * @param packageInfo + * PackageInfo to retrieve signature keys + * @return Signature[] containing given PackageInfo signature keys + */ + @SuppressWarnings("unused") + private Signature[] getSignature(PackageInfo packageInfo) { + + Signature[] sigs = packageInfo.signatures; + return sigs; + + } + + /** + * Get Platform signature key. we check signature of SystemUI process or + * Phone process + * + * @return Signature platform sign key or null if not found + */ + @SuppressWarnings("unused") + private Signature getPlatformSignature() { + Signature sig = getSignature(SYSTEM_UI)[0]; + if (sig == null) { + sig = getSignature(PHONE)[0]; + } + return sig; + } + + /** + * Filter given ApplicationInfo list only to apps that are not platform + * signed + * + * @param appList + * List with a list of apps to filter + * @return List filtred app list + */ + private List filterToNotPlatformKeyAppList(List packageList) { + if (packageList != null) { + + List noPlatformKeyAppList = new ArrayList(); + for (PackageInfo packageInfo : packageList) { + try { + packageInfo = mPm.getPackageInfo(packageInfo.packageName, + PackageManager.GET_ACTIVITIES + | PackageManager.GET_PERMISSIONS + | PackageManager.GET_SIGNATURES); + } catch (NameNotFoundException e) { + continue; + } + // Check signature match with SystemUI + if (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (packageInfo.activities != null && packageInfo.activities.length > 0)) + && getPm().checkSignatures(SYSTEM_UI, + packageInfo.packageName) != PackageManager.SIGNATURE_MATCH) { + noPlatformKeyAppList.add(packageInfo); + } + } + return noPlatformKeyAppList; + } else { + return null; + } + } + + private PackageInfo getInstalledAppDetails(String packageName) { + PackageInfo info = null; + try { + info = getPm().getPackageInfo( + packageName, + PackageManager.GET_META_DATA + | PackageManager.GET_PERMISSIONS + | PackageManager.GET_SIGNATURES); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return info; + } + + @Override + public List loadInBackground() { + List appList = mPm.getInstalledPackages(0); + + appList = filterToNotPlatformKeyAppList(appList); + + HashMap appDetailsHash = new HashMap( + appList.size()); + if (mSubcategories.size() == 0) { + for (PackageInfo pInfo : appList) { + appDetailsHash.put(pInfo.packageName, new AppDetails(this, + pInfo)); + } + } else { + for (PackageInfo packageInfo : appList) { + String[] requestedPermissions = packageInfo.requestedPermissions; + if (requestedPermissions != null) { + AppDetails appDetails = appDetailsHash.get(packageInfo.packageName); + for (String permissionName : requestedPermissions) { + Integer subcategories = mAllPermissionsHash.get(permissionName); + if (subcategories != null + && (appDetails == null || !appDetails.isInAllSubcategories(subcategories)) + && getPm().checkPermission(permissionName, + packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + if (appDetails == null) { + appDetails = new AppDetails(this, packageInfo); + appDetailsHash.put(packageInfo.packageName, + appDetails); + } + appDetails.addSubcategory(subcategories); + } + } + } + } + } + // Sort the list. + List list = new ArrayList( + appDetailsHash.values()); + Collections.sort(list, AppDetails.SMART_COMPARATOR); + + for (Category category : mCategories) + category.assignAppsToCategory(list); + + mWasDataReloaded = true; + return list; + } + + /** + * 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 apps) { + if (isReset()) { + apps = null; + } + mAppDetailsList = apps; + + if (isStarted()) { + // If the Loader is currently started, we can immediately + // deliver its results. + super.deliverResult(apps); + } + } + + /** + * Handles a request to start the Loader. + */ + @Override + protected void onStartLoading() { + if (mAppDetailsList != null) { + // If we currently have a result available, deliver it + // immediately. + deliverResult(mAppDetailsList); + } + + // Start watching for changes in the app data. + if (mPackageObserver == null) { + mPackageObserver = new PackageIntentReceiver(this); + } + + if (takeContentChanged() || mAppDetailsList == null) { + // 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 completely reset the Loader. + */ + @Override + protected void onReset() { + + // Ensure the loader is stopped + onStopLoading(); + + // Stop monitoring for changes. + if (mPackageObserver != null) { + getContext().unregisterReceiver(mPackageObserver); + mPackageObserver = null; + } + } + + /** + * Gets AppDetails from mAppDetailsList for a given PackageName + * + * @param packageName + * PackageName for which to get AppDetails + * @return AppDetails for given PackageName + */ + public static AppDetails getAppDetails(String packageName) { + if (packageName != null && mAppDetailsList != null) { + for (AppDetails appDetails : mAppDetailsList) { + if (packageName.equals(appDetails.getAppPackageName())) { + return appDetails; + } + } + } + return null; + } + + public static void setOnChangeListener(OnAppRemoveListener listener) { + mChangeListener = listener; + } + + /** + * @return the mAppDetailsList + */ + public static List getAppDetailsList() { + return mAppDetailsList; + } + + /** + * @return the mCategories + */ + static public List getCategories() { + return mCategories; + } + + public static List getSubcategoriesOfMask(int subcategoriesMask) { + ArrayList list = new ArrayList(); + for (Subcategory subcategory : mSubcategories) { + if ((subcategoriesMask & subcategory.getId()) > 0) { + list.add(subcategory); + } + } + return list; + } + + public PackageManager getPm() { + return mPm; + } + + public interface OnAppRemoveListener { + public void onPackageRemoved(String packageName); + } + + /** + * Helper class to look for interesting changes to the installed apps so + * that the loader can be updated. + */ + public static class PackageIntentReceiver extends BroadcastReceiver { + private 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); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals( + Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)) { + mLoader.onPackagesAdded(intent + .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)); + } else if (intent.getAction().equals( + Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { + mLoader.onPackagesRemoved(intent + .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)); + } else if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) + mLoader.onPackageRemoved(intent.getData() + .getSchemeSpecificPart(), true); + else if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) + mLoader.onPackageAdded( + intent.getData().getSchemeSpecificPart(), true); + else if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) + mLoader.onPackageChanged(intent.getData() + .getSchemeSpecificPart()); + } + } + + public void onPackageRemoved(String packageName, boolean deliverResult) { + if (mChangeListener != null) + mChangeListener.onPackageRemoved(packageName); + for (AppDetails details : mAppDetailsList) { + if (details.getAppPackageName().equals(packageName)) { + mAppDetailsList.remove(details); + for (Category category : mCategories) + category.removeAppFromList(details); + if (deliverResult) + deliverResult(new ArrayList(mAppDetailsList)); + break; + } + } + } + + public void onPackagesRemoved(String[] packageNames) { + for (String packageName : packageNames) { + onPackageRemoved(packageName, false); + } + deliverResult(new ArrayList(mAppDetailsList)); + } + + public void onPackageChanged(String packageName) { + for (AppDetails details : mAppDetailsList) { + if (details.getAppPackageName().equals(packageName)) { + PackageInfo packageInfo = getInstalledAppDetails(packageName); + if (packageInfo == null) + return; + details.setEnabled(packageInfo.applicationInfo.enabled); + deliverResult(new ArrayList(mAppDetailsList)); + break; + } + } + } + + public void onPackageAdded(String packageName, boolean deliverResult) { + PackageInfo packageInfo = getInstalledAppDetails(packageName); + if (packageInfo == null) + return; + AppDetails newPackage = new AppDetails(this, packageInfo); + + String[] requestedPermissions = packageInfo.requestedPermissions; + if (requestedPermissions != null && mSubcategories != null) { + for (String permissionName : requestedPermissions) { + Integer subcategories = mAllPermissionsHash.get(permissionName); + if (subcategories != null + && getPm().checkPermission(permissionName, + packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + newPackage.addSubcategory(subcategories); + } + } + } + mAppDetailsList.add(newPackage); + + for (Category category : mCategories) + category.addApplicationToCategory(newPackage); + if (deliverResult) + deliverResult(new ArrayList(mAppDetailsList)); + } + + public void onPackagesAdded(String[] packageNames) { + for (String packageStr : packageNames) { + onPackageAdded(packageStr, false); + } + + deliverResult(new ArrayList(mAppDetailsList)); + } + + public boolean wasDataReloaded() { + return mWasDataReloaded; + } + + public void resetWasDataReloaded() { + mWasDataReloaded = false; + } +} \ No newline at end of file