2 Copyright (C) 2014 Samsung Electronics Polska Sp. z o.o.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU AFFERO General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8 You may obtain a copy of the License at
10 http://www.gnu.org/licenses/agpl-3.0.txt
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 package com.samsung.srpol.loader;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
29 import org.xmlpull.v1.XmlPullParserException;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageManager.NameNotFoundException;
39 import android.content.pm.Signature;
40 import android.content.res.XmlResourceParser;
41 import android.support.v4.content.AsyncTaskLoader;
43 import com.samsung.srpol.R;
44 import com.samsung.srpol.data.Category;
45 import com.samsung.srpol.data.Subcategory;
48 * A custom Loader that loads all of the installed applications.
50 public class AppListLoader extends AsyncTaskLoader<List<AppDetails>> {
51 private static final String SYSTEM_UI = "com.android.systemui";
52 private static final String PHONE = "com.android.phone";
54 public static final String PREF_INCLUDE_SYSTEM_APPS = "include_system_apps";
56 private final PackageManager mPm;
58 private static List<AppDetails> mAppDetailsList = null;
59 private static List<Category> mCategories;
60 private static ArrayList<Subcategory> mSubcategories;
61 private static HashMap<String, Integer> mAllPermissionsHash;
63 private PackageIntentReceiver mPackageObserver;
64 private boolean mWasDataReloaded = true;
65 private static OnAppRemoveListener mChangeListener = null;
67 public AppListLoader(Context context) {
70 if (mCategories == null || mSubcategories == null
71 || mAllPermissionsHash == null)
72 initCategories(context);
74 mPm = getContext().getPackageManager();
77 private void initCategories(Context context) {
78 mCategories = new ArrayList<Category>();
79 mSubcategories = new ArrayList<Subcategory>();
80 Subcategory.resetGenerator();
82 // Reading "data send" subcategory as it is common
84 XmlResourceParser parser = context.getResources().getXml(
85 R.xml.data_send_subcategory);
87 while (eventType != XmlResourceParser.END_DOCUMENT
88 && eventType != XmlResourceParser.START_TAG)
89 eventType = parser.next();
90 if (eventType != XmlResourceParser.END_DOCUMENT) {
91 Subcategory subcategory = readSubCategory(parser);
92 mSubcategories.add(subcategory);
94 } catch (XmlPullParserException ex) {
96 } catch (IOException e) {
100 // Reading all categories and subcategories from xml
102 XmlResourceParser parser = context.getResources().getXml(
105 while (eventType != XmlResourceParser.END_DOCUMENT
106 && eventType != XmlResourceParser.START_TAG)
107 eventType = parser.next();
108 if (eventType != XmlResourceParser.END_DOCUMENT) {
109 parser.require(XmlResourceParser.START_TAG, null, "container");
110 while (parser.next() != XmlResourceParser.END_TAG) {
111 if (parser.getEventType() != XmlResourceParser.START_TAG)
113 if (!parser.getName().equals("category"))
115 Category cat = readCategory(parser);
116 mCategories.add(cat);
117 mSubcategories.addAll(cat.getSubCategories());
120 } catch (XmlPullParserException ex) {
121 ex.printStackTrace();
122 } catch (IOException e) {
126 mAllPermissionsHash = new HashMap<String, Integer>();
127 for (Subcategory subcat : mSubcategories) {
128 for (String perm : subcat.getPermissions()) {
129 Integer value = mAllPermissionsHash.get(perm);
131 mAllPermissionsHash.put(perm, Integer.valueOf(subcat.getId()));
133 value |= subcat.getId();
134 mAllPermissionsHash.put(perm, value);
140 private Category readCategory(XmlResourceParser parser)
141 throws XmlPullParserException, IOException {
142 parser.require(XmlResourceParser.START_TAG, null, "category");
143 String title = null, header = null, description = null, shortDescription = null, link = null;
145 boolean dataSend = false;
146 ArrayList<Subcategory> subCategories = new ArrayList<Subcategory>();
148 while (parser.next() != XmlResourceParser.END_TAG) {
149 if (parser.getEventType() != XmlResourceParser.START_TAG)
151 String name = parser.getName();
152 if (name.equals("title")) {
153 title = readTextElement(parser, name);
154 } else if (name.equals("header")) {
155 header = readTextElement(parser, name);
156 } else if (name.equals("short_description")) {
157 shortDescription = readTextElement(parser, name);
158 } else if (name.equals("description")) {
159 description = readTextElement(parser, name);
160 } else if (name.equals("icon")) {
161 icon = getContext().getResources().getIdentifier(
162 readTextElement(parser, name), "drawable",
163 getContext().getPackageName());
164 } else if (name.equals("link")) {
165 link = readTextElement(parser, name);
166 } else if (name.equals("dataSend")) {
167 dataSend = Boolean.parseBoolean(readTextElement(parser, name));
168 } else if (name.equals("subcategories")) {
169 while (parser.next() != XmlResourceParser.END_TAG) {
170 subCategories.add(readSubCategory(parser));
175 return new Category(getContext(), title, header, shortDescription,
176 description, icon, link, dataSend, subCategories);
179 private Subcategory readSubCategory(XmlResourceParser parser)
180 throws XmlPullParserException, IOException {
181 parser.require(XmlResourceParser.START_TAG, null, "subcategory");
182 String header = null, description = null, icon = null;
183 ArrayList<String> permissions = new ArrayList<String>();
185 while (parser.next() != XmlResourceParser.END_TAG) {
186 if (parser.getEventType() != XmlResourceParser.START_TAG)
188 String name = parser.getName();
189 if (name.equals("header")) {
190 header = readTextElement(parser, name);
191 } else if (name.equals("description")) {
192 description = readTextElement(parser, name);
193 } else if (name.equals("icon")) {
194 icon = readTextElement(parser, name);
195 } else if (name.equals("permissions")) {
196 while (parser.next() != XmlResourceParser.END_TAG) {
197 permissions.add(readTextElement(parser, "item"));
203 return new Subcategory(getContext(), header, description, icon,
207 private String readTextElement(XmlResourceParser parser, String element)
208 throws XmlPullParserException, IOException {
209 parser.require(XmlResourceParser.START_TAG, null, element);
210 String title = readText(parser);
211 parser.require(XmlResourceParser.END_TAG, null, element);
215 private String readText(XmlResourceParser parser)
216 throws XmlPullParserException, IOException {
218 if (parser.next() == XmlResourceParser.TEXT) {
219 result = parser.getText();
225 private void skip(XmlResourceParser parser) throws XmlPullParserException,
227 if (parser.getEventType() != XmlResourceParser.START_TAG) {
228 throw new IllegalStateException();
232 switch (parser.next()) {
233 case XmlResourceParser.END_TAG:
236 case XmlResourceParser.START_TAG:
244 * Get Signature keys of given package
247 * package name to retrieve signature keys
248 * @return Signature[] containing given package name signature keys
250 private Signature[] getSignature(String packageName) {
253 PackageInfo packageInfo = getPm().getPackageInfo(packageName,
254 PackageManager.GET_SIGNATURES);
255 Signature[] sigs = packageInfo.signatures;
257 } catch (NameNotFoundException e) {
264 * Get Signature keys for given PackageInfo
267 * PackageInfo to retrieve signature keys
268 * @return Signature[] containing given PackageInfo signature keys
270 @SuppressWarnings("unused")
271 private Signature[] getSignature(PackageInfo packageInfo) {
273 Signature[] sigs = packageInfo.signatures;
279 * Get Platform signature key. we check signature of SystemUI process or
282 * @return Signature platform sign key or null if not found
284 @SuppressWarnings("unused")
285 private Signature getPlatformSignature() {
286 Signature sig = getSignature(SYSTEM_UI)[0];
288 sig = getSignature(PHONE)[0];
294 * Filter given ApplicationInfo list only to apps that are not platform
298 * List<ApplicationInfo> with a list of apps to filter
299 * @return List<ApplicationInfo> filtred app list
301 private List<PackageInfo> filterToNotPlatformKeyAppList(List<PackageInfo> packageList) {
302 if (packageList != null) {
304 List<PackageInfo> noPlatformKeyAppList = new ArrayList<PackageInfo>();
305 for (PackageInfo packageInfo : packageList) {
307 packageInfo = mPm.getPackageInfo(packageInfo.packageName,
308 PackageManager.GET_ACTIVITIES
309 | PackageManager.GET_PERMISSIONS
310 | PackageManager.GET_SIGNATURES);
311 } catch (NameNotFoundException e) {
314 // Check signature match with SystemUI
315 if (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (packageInfo.activities != null && packageInfo.activities.length > 0))
316 && getPm().checkSignatures(SYSTEM_UI,
317 packageInfo.packageName) != PackageManager.SIGNATURE_MATCH) {
318 noPlatformKeyAppList.add(packageInfo);
321 return noPlatformKeyAppList;
327 private PackageInfo getInstalledAppDetails(String packageName) {
328 PackageInfo info = null;
330 info = getPm().getPackageInfo(
332 PackageManager.GET_META_DATA
333 | PackageManager.GET_PERMISSIONS
334 | PackageManager.GET_SIGNATURES);
335 } catch (NameNotFoundException e) {
342 public List<AppDetails> loadInBackground() {
343 List<PackageInfo> appList = mPm.getInstalledPackages(0);
345 appList = filterToNotPlatformKeyAppList(appList);
347 HashMap<String, AppDetails> appDetailsHash = new HashMap<String, AppDetails>(
349 if (mSubcategories.size() == 0) {
350 for (PackageInfo pInfo : appList) {
351 appDetailsHash.put(pInfo.packageName, new AppDetails(this,
355 for (PackageInfo packageInfo : appList) {
356 String[] requestedPermissions = packageInfo.requestedPermissions;
357 if (requestedPermissions != null) {
358 AppDetails appDetails = appDetailsHash.get(packageInfo.packageName);
359 for (String permissionName : requestedPermissions) {
360 Integer subcategories = mAllPermissionsHash.get(permissionName);
361 if (subcategories != null
362 && (appDetails == null || !appDetails.isInAllSubcategories(subcategories))
363 && getPm().checkPermission(permissionName,
364 packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
365 if (appDetails == null) {
366 appDetails = new AppDetails(this, packageInfo);
367 appDetailsHash.put(packageInfo.packageName,
370 appDetails.addSubcategory(subcategories);
377 List<AppDetails> list = new ArrayList<AppDetails>(
378 appDetailsHash.values());
379 Collections.sort(list, AppDetails.SMART_COMPARATOR);
381 for (Category category : mCategories)
382 category.assignAppsToCategory(list);
384 mWasDataReloaded = true;
389 * Called when there is new data to deliver to the client. The super class
390 * will take care of delivering it; the implementation here just adds a
394 public void deliverResult(List<AppDetails> apps) {
398 mAppDetailsList = apps;
401 // If the Loader is currently started, we can immediately
402 // deliver its results.
403 super.deliverResult(apps);
408 * Handles a request to start the Loader.
411 protected void onStartLoading() {
412 if (mAppDetailsList != null) {
413 // If we currently have a result available, deliver it
415 deliverResult(mAppDetailsList);
418 // Start watching for changes in the app data.
419 if (mPackageObserver == null) {
420 mPackageObserver = new PackageIntentReceiver(this);
423 if (takeContentChanged() || mAppDetailsList == null) {
424 // If the data has changed since the last time it was loaded
425 // or is not currently available, start a load.
431 * Handles a request to stop the Loader.
434 protected void onStopLoading() {
435 // Attempt to cancel the current load task if possible.
440 * Handles a request to completely reset the Loader.
443 protected void onReset() {
445 // Ensure the loader is stopped
448 // Stop monitoring for changes.
449 if (mPackageObserver != null) {
450 getContext().unregisterReceiver(mPackageObserver);
451 mPackageObserver = null;
456 * Gets AppDetails from mAppDetailsList for a given PackageName
459 * PackageName for which to get AppDetails
460 * @return AppDetails for given PackageName
462 public static AppDetails getAppDetails(String packageName) {
463 if (packageName != null && mAppDetailsList != null) {
464 for (AppDetails appDetails : mAppDetailsList) {
465 if (packageName.equals(appDetails.getAppPackageName())) {
473 public static void setOnChangeListener(OnAppRemoveListener listener) {
474 mChangeListener = listener;
478 * @return the mAppDetailsList
480 public static List<AppDetails> getAppDetailsList() {
481 return mAppDetailsList;
485 * @return the mCategories
487 static public List<Category> getCategories() {
491 public static List<Subcategory> getSubcategoriesOfMask(int subcategoriesMask) {
492 ArrayList<Subcategory> list = new ArrayList<Subcategory>();
493 for (Subcategory subcategory : mSubcategories) {
494 if ((subcategoriesMask & subcategory.getId()) > 0) {
495 list.add(subcategory);
501 public PackageManager getPm() {
505 public interface OnAppRemoveListener {
506 public void onPackageRemoved(String packageName);
510 * Helper class to look for interesting changes to the installed apps so
511 * that the loader can be updated.
513 public static class PackageIntentReceiver extends BroadcastReceiver {
514 private final AppListLoader mLoader;
516 public PackageIntentReceiver(AppListLoader loader) {
518 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
519 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
520 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
521 filter.addDataScheme("package");
522 mLoader.getContext().registerReceiver(this, filter);
523 // Register for events related to sdcard installation.
524 IntentFilter sdFilter = new IntentFilter();
525 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
526 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
527 mLoader.getContext().registerReceiver(this, sdFilter);
531 public void onReceive(Context context, Intent intent) {
532 if (intent.getAction().equals(
533 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)) {
534 mLoader.onPackagesAdded(intent
535 .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST));
536 } else if (intent.getAction().equals(
537 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
538 mLoader.onPackagesRemoved(intent
539 .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST));
540 } else if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED))
541 mLoader.onPackageRemoved(intent.getData()
542 .getSchemeSpecificPart(), true);
543 else if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED))
544 mLoader.onPackageAdded(
545 intent.getData().getSchemeSpecificPart(), true);
546 else if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED))
547 mLoader.onPackageChanged(intent.getData()
548 .getSchemeSpecificPart());
552 public void onPackageRemoved(String packageName, boolean deliverResult) {
553 if (mChangeListener != null)
554 mChangeListener.onPackageRemoved(packageName);
555 for (AppDetails details : mAppDetailsList) {
556 if (details.getAppPackageName().equals(packageName)) {
557 mAppDetailsList.remove(details);
558 for (Category category : mCategories)
559 category.removeAppFromList(details);
561 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
567 public void onPackagesRemoved(String[] packageNames) {
568 for (String packageName : packageNames) {
569 onPackageRemoved(packageName, false);
571 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
574 public void onPackageChanged(String packageName) {
575 for (AppDetails details : mAppDetailsList) {
576 if (details.getAppPackageName().equals(packageName)) {
577 PackageInfo packageInfo = getInstalledAppDetails(packageName);
578 if (packageInfo == null)
580 details.setEnabled(packageInfo.applicationInfo.enabled);
581 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
587 public void onPackageAdded(String packageName, boolean deliverResult) {
588 PackageInfo packageInfo = getInstalledAppDetails(packageName);
589 if (packageInfo == null)
591 AppDetails newPackage = new AppDetails(this, packageInfo);
593 String[] requestedPermissions = packageInfo.requestedPermissions;
594 if (requestedPermissions != null && mSubcategories != null) {
595 for (String permissionName : requestedPermissions) {
596 Integer subcategories = mAllPermissionsHash.get(permissionName);
597 if (subcategories != null
598 && getPm().checkPermission(permissionName,
599 packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
600 newPackage.addSubcategory(subcategories);
604 mAppDetailsList.add(newPackage);
606 for (Category category : mCategories)
607 category.addApplicationToCategory(newPackage);
609 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
612 public void onPackagesAdded(String[] packageNames) {
613 for (String packageStr : packageNames) {
614 onPackageAdded(packageStr, false);
617 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
620 public boolean wasDataReloaded() {
621 return mWasDataReloaded;
624 public void resetWasDataReloaded() {
625 mWasDataReloaded = false;