Initial commit.
[mobilnebezpieczenstwo.git] / src / com / samsung / srpol / loader / AppListLoader.java
1 /*
2    Copyright (C) 2014  Samsung Electronics Polska Sp. z o.o.
3
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
9
10                 http://www.gnu.org/licenses/agpl-3.0.txt
11
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.
16
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/>.
19 */
20
21 package com.samsung.srpol.loader;
22
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;
28
29 import org.xmlpull.v1.XmlPullParserException;
30
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;
42
43 import com.samsung.srpol.R;
44 import com.samsung.srpol.data.Category;
45 import com.samsung.srpol.data.Subcategory;
46
47 /**
48  * A custom Loader that loads all of the installed applications.
49  */
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";
53
54     public static final String PREF_INCLUDE_SYSTEM_APPS = "include_system_apps";
55
56     private final PackageManager mPm;
57
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;
62
63     private PackageIntentReceiver mPackageObserver;
64     private boolean mWasDataReloaded = true;
65     private static OnAppRemoveListener mChangeListener = null;
66
67     public AppListLoader(Context context) {
68         super(context);
69
70         if (mCategories == null || mSubcategories == null
71                 || mAllPermissionsHash == null)
72             initCategories(context);
73
74         mPm = getContext().getPackageManager();
75     }
76
77     private void initCategories(Context context) {
78         mCategories = new ArrayList<Category>();
79         mSubcategories = new ArrayList<Subcategory>();
80         Subcategory.resetGenerator();
81
82         // Reading "data send" subcategory as it is common
83         try {
84             XmlResourceParser parser = context.getResources().getXml(
85                     R.xml.data_send_subcategory);
86             int eventType = 0;
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);
93             }
94         } catch (XmlPullParserException ex) {
95             ex.printStackTrace();
96         } catch (IOException e) {
97             e.printStackTrace();
98         }
99
100         // Reading all categories and subcategories from xml
101         try {
102             XmlResourceParser parser = context.getResources().getXml(
103                     R.xml.categories);
104             int eventType = 0;
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)
112                         continue;
113                     if (!parser.getName().equals("category"))
114                         continue;
115                     Category cat = readCategory(parser);
116                     mCategories.add(cat);
117                     mSubcategories.addAll(cat.getSubCategories());
118                 }
119             }
120         } catch (XmlPullParserException ex) {
121             ex.printStackTrace();
122         } catch (IOException e) {
123             e.printStackTrace();
124         }
125
126         mAllPermissionsHash = new HashMap<String, Integer>();
127         for (Subcategory subcat : mSubcategories) {
128             for (String perm : subcat.getPermissions()) {
129                 Integer value = mAllPermissionsHash.get(perm);
130                 if (value == null) {
131                     mAllPermissionsHash.put(perm, Integer.valueOf(subcat.getId()));
132                 } else {
133                     value |= subcat.getId();
134                     mAllPermissionsHash.put(perm, value);
135                 }
136             }
137         }
138     }
139
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;
144         int icon = 0;
145         boolean dataSend = false;
146         ArrayList<Subcategory> subCategories = new ArrayList<Subcategory>();
147
148         while (parser.next() != XmlResourceParser.END_TAG) {
149             if (parser.getEventType() != XmlResourceParser.START_TAG)
150                 continue;
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));
171                 }
172             } else
173                 skip(parser);
174         }
175         return new Category(getContext(), title, header, shortDescription,
176                 description, icon, link, dataSend, subCategories);
177     }
178
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>();
184
185         while (parser.next() != XmlResourceParser.END_TAG) {
186             if (parser.getEventType() != XmlResourceParser.START_TAG)
187                 continue;
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"));
198                 }
199             } else
200                 skip(parser);
201         }
202
203         return new Subcategory(getContext(), header, description, icon,
204                 permissions);
205     }
206
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);
212         return title;
213     }
214
215     private String readText(XmlResourceParser parser)
216             throws XmlPullParserException, IOException {
217         String result = "";
218         if (parser.next() == XmlResourceParser.TEXT) {
219             result = parser.getText();
220             parser.nextTag();
221         }
222         return result;
223     }
224
225     private void skip(XmlResourceParser parser) throws XmlPullParserException,
226             IOException {
227         if (parser.getEventType() != XmlResourceParser.START_TAG) {
228             throw new IllegalStateException();
229         }
230         int depth = 1;
231         while (depth != 0) {
232             switch (parser.next()) {
233             case XmlResourceParser.END_TAG:
234                 depth--;
235                 break;
236             case XmlResourceParser.START_TAG:
237                 depth++;
238                 break;
239             }
240         }
241     }
242
243     /**
244      * Get Signature keys of given package
245      * 
246      * @param packageName
247      *            package name to retrieve signature keys
248      * @return Signature[] containing given package name signature keys
249      */
250     private Signature[] getSignature(String packageName) {
251
252         try {
253             PackageInfo packageInfo = getPm().getPackageInfo(packageName,
254                     PackageManager.GET_SIGNATURES);
255             Signature[] sigs = packageInfo.signatures;
256             return sigs;
257         } catch (NameNotFoundException e) {
258             return null;
259         }
260
261     }
262
263     /**
264      * Get Signature keys for given PackageInfo
265      * 
266      * @param packageInfo
267      *            PackageInfo to retrieve signature keys
268      * @return Signature[] containing given PackageInfo signature keys
269      */
270     @SuppressWarnings("unused")
271     private Signature[] getSignature(PackageInfo packageInfo) {
272
273         Signature[] sigs = packageInfo.signatures;
274         return sigs;
275
276     }
277
278     /**
279      * Get Platform signature key. we check signature of SystemUI process or
280      * Phone process
281      * 
282      * @return Signature platform sign key or null if not found
283      */
284     @SuppressWarnings("unused")
285     private Signature getPlatformSignature() {
286         Signature sig = getSignature(SYSTEM_UI)[0];
287         if (sig == null) {
288             sig = getSignature(PHONE)[0];
289         }
290         return sig;
291     }
292
293     /**
294      * Filter given ApplicationInfo list only to apps that are not platform
295      * signed
296      * 
297      * @param appList
298      *            List<ApplicationInfo> with a list of apps to filter
299      * @return List<ApplicationInfo> filtred app list
300      */
301     private List<PackageInfo> filterToNotPlatformKeyAppList(List<PackageInfo> packageList) {
302         if (packageList != null) {
303
304             List<PackageInfo> noPlatformKeyAppList = new ArrayList<PackageInfo>();
305             for (PackageInfo packageInfo : packageList) {
306                 try {
307                     packageInfo = mPm.getPackageInfo(packageInfo.packageName,
308                             PackageManager.GET_ACTIVITIES
309                                     | PackageManager.GET_PERMISSIONS
310                                     | PackageManager.GET_SIGNATURES);
311                 } catch (NameNotFoundException e) {
312                     continue;
313                 }
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);
319                 }
320             }
321             return noPlatformKeyAppList;
322         } else {
323             return null;
324         }
325     }
326
327     private PackageInfo getInstalledAppDetails(String packageName) {
328         PackageInfo info = null;
329         try {
330             info = getPm().getPackageInfo(
331                     packageName,
332                     PackageManager.GET_META_DATA
333                             | PackageManager.GET_PERMISSIONS
334                             | PackageManager.GET_SIGNATURES);
335         } catch (NameNotFoundException e) {
336             e.printStackTrace();
337         }
338         return info;
339     }
340
341     @Override
342     public List<AppDetails> loadInBackground() {
343         List<PackageInfo> appList = mPm.getInstalledPackages(0);
344
345         appList = filterToNotPlatformKeyAppList(appList);
346
347         HashMap<String, AppDetails> appDetailsHash = new HashMap<String, AppDetails>(
348                 appList.size());
349         if (mSubcategories.size() == 0) {
350             for (PackageInfo pInfo : appList) {
351                 appDetailsHash.put(pInfo.packageName, new AppDetails(this,
352                         pInfo));
353             }
354         } else {
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,
368                                         appDetails);
369                             }
370                             appDetails.addSubcategory(subcategories);
371                         }
372                     }
373                 }
374             }
375         }
376         // Sort the list.
377         List<AppDetails> list = new ArrayList<AppDetails>(
378                 appDetailsHash.values());
379         Collections.sort(list, AppDetails.SMART_COMPARATOR);
380
381         for (Category category : mCategories)
382             category.assignAppsToCategory(list);
383
384         mWasDataReloaded = true;
385         return list;
386     }
387
388     /**
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
391      * little more logic.
392      */
393     @Override
394     public void deliverResult(List<AppDetails> apps) {
395         if (isReset()) {
396             apps = null;
397         }
398         mAppDetailsList = apps;
399
400         if (isStarted()) {
401             // If the Loader is currently started, we can immediately
402             // deliver its results.
403             super.deliverResult(apps);
404         }
405     }
406
407     /**
408      * Handles a request to start the Loader.
409      */
410     @Override
411     protected void onStartLoading() {
412         if (mAppDetailsList != null) {
413             // If we currently have a result available, deliver it
414             // immediately.
415             deliverResult(mAppDetailsList);
416         }
417
418         // Start watching for changes in the app data.
419         if (mPackageObserver == null) {
420             mPackageObserver = new PackageIntentReceiver(this);
421         }
422
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.
426             forceLoad();
427         }
428     }
429
430     /**
431      * Handles a request to stop the Loader.
432      */
433     @Override
434     protected void onStopLoading() {
435         // Attempt to cancel the current load task if possible.
436         cancelLoad();
437     }
438
439     /**
440      * Handles a request to completely reset the Loader.
441      */
442     @Override
443     protected void onReset() {
444
445         // Ensure the loader is stopped
446         onStopLoading();
447
448         // Stop monitoring for changes.
449         if (mPackageObserver != null) {
450             getContext().unregisterReceiver(mPackageObserver);
451             mPackageObserver = null;
452         }
453     }
454
455     /**
456      * Gets AppDetails from mAppDetailsList for a given PackageName
457      * 
458      * @param packageName
459      *            PackageName for which to get AppDetails
460      * @return AppDetails for given PackageName
461      */
462     public static AppDetails getAppDetails(String packageName) {
463         if (packageName != null && mAppDetailsList != null) {
464             for (AppDetails appDetails : mAppDetailsList) {
465                 if (packageName.equals(appDetails.getAppPackageName())) {
466                     return appDetails;
467                 }
468             }
469         }
470         return null;
471     }
472
473     public static void setOnChangeListener(OnAppRemoveListener listener) {
474         mChangeListener = listener;
475     }
476
477     /**
478      * @return the mAppDetailsList
479      */
480     public static List<AppDetails> getAppDetailsList() {
481         return mAppDetailsList;
482     }
483
484     /**
485      * @return the mCategories
486      */
487     static public List<Category> getCategories() {
488         return mCategories;
489     }
490
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);
496             }
497         }
498         return list;
499     }
500
501     public PackageManager getPm() {
502         return mPm;
503     }
504
505     public interface OnAppRemoveListener {
506         public void onPackageRemoved(String packageName);
507     }
508
509     /**
510      * Helper class to look for interesting changes to the installed apps so
511      * that the loader can be updated.
512      */
513     public static class PackageIntentReceiver extends BroadcastReceiver {
514         private final AppListLoader mLoader;
515
516         public PackageIntentReceiver(AppListLoader loader) {
517             mLoader = 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);
528         }
529
530         @Override
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());
549         }
550     }
551
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);
560                 if (deliverResult)
561                     deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
562                 break;
563             }
564         }
565     }
566
567     public void onPackagesRemoved(String[] packageNames) {
568         for (String packageName : packageNames) {
569             onPackageRemoved(packageName, false);
570         }
571         deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
572     }
573
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)
579                     return;
580                 details.setEnabled(packageInfo.applicationInfo.enabled);
581                 deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
582                 break;
583             }
584         }
585     }
586
587     public void onPackageAdded(String packageName, boolean deliverResult) {
588         PackageInfo packageInfo = getInstalledAppDetails(packageName);
589         if (packageInfo == null)
590             return;
591         AppDetails newPackage = new AppDetails(this, packageInfo);
592
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);
601                 }
602             }
603         }
604         mAppDetailsList.add(newPackage);
605
606         for (Category category : mCategories)
607             category.addApplicationToCategory(newPackage);
608         if (deliverResult)
609             deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
610     }
611
612     public void onPackagesAdded(String[] packageNames) {
613         for (String packageStr : packageNames) {
614             onPackageAdded(packageStr, false);
615         }
616
617         deliverResult(new ArrayList<AppDetails>(mAppDetailsList));
618     }
619
620     public boolean wasDataReloaded() {
621         return mWasDataReloaded;
622     }
623
624     public void resetWasDataReloaded() {
625         mWasDataReloaded = false;
626     }
627 }