Migrate to gradle, appcompat as external dependency.
[mobilnebezpieczenstwo.git] / 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 (file)
index 0000000..3dfdb0f
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+*/
+
+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<List<AppDetails>> {
+    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<AppDetails> mAppDetailsList = null;
+    private static List<Category> mCategories;
+    private static ArrayList<Subcategory> mSubcategories;
+    private static HashMap<String, Integer> 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<Category>();
+        mSubcategories = new ArrayList<Subcategory>();
+        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<String, Integer>();
+        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<Subcategory> subCategories = new ArrayList<Subcategory>();
+
+        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<String> permissions = new ArrayList<String>();
+
+        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<ApplicationInfo> with a list of apps to filter
+     * @return List<ApplicationInfo> filtred app list
+     */
+    private List<PackageInfo> filterToNotPlatformKeyAppList(List<PackageInfo> packageList) {
+        if (packageList != null) {
+
+            List<PackageInfo> noPlatformKeyAppList = new ArrayList<PackageInfo>();
+            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<AppDetails> loadInBackground() {
+        List<PackageInfo> appList = mPm.getInstalledPackages(0);
+
+        appList = filterToNotPlatformKeyAppList(appList);
+
+        HashMap<String, AppDetails> appDetailsHash = new HashMap<String, AppDetails>(
+                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<AppDetails> list = new ArrayList<AppDetails>(
+                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<AppDetails> 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<AppDetails> getAppDetailsList() {
+        return mAppDetailsList;
+    }
+
+    /**
+     * @return the mCategories
+     */
+    static public List<Category> getCategories() {
+        return mCategories;
+    }
+
+    public static List<Subcategory> getSubcategoriesOfMask(int subcategoriesMask) {
+        ArrayList<Subcategory> list = new ArrayList<Subcategory>();
+        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<AppDetails>(mAppDetailsList));
+                break;
+            }
+        }
+    }
+
+    public void onPackagesRemoved(String[] packageNames) {
+        for (String packageName : packageNames) {
+            onPackageRemoved(packageName, false);
+        }
+        deliverResult(new ArrayList<AppDetails>(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<AppDetails>(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<AppDetails>(mAppDetailsList));
+    }
+
+    public void onPackagesAdded(String[] packageNames) {
+        for (String packageStr : packageNames) {
+            onPackageAdded(packageStr, false);
+        }
+
+        deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
+    }
+
+    public boolean wasDataReloaded() {
+        return mWasDataReloaded;
+    }
+
+    public void resetWasDataReloaded() {
+        mWasDataReloaded = false;
+    }
+}
\ No newline at end of file