1/*
2* Copyright (C) 2015 The Android Open Source Project
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8*      http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17package com.android.packageinstaller.permission.ui.television;
18
19import android.Manifest;
20import android.app.ActionBar;
21import android.app.AlertDialog;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageItemInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.PermissionGroupInfo;
30import android.content.pm.PermissionInfo;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.Build;
34import android.os.Bundle;
35import android.provider.Settings;
36import android.support.v14.preference.SwitchPreference;
37import android.support.v7.preference.Preference;
38import android.support.v7.preference.Preference.OnPreferenceChangeListener;
39import android.support.v7.preference.Preference.OnPreferenceClickListener;
40import android.support.v7.preference.PreferenceCategory;
41import android.support.v7.preference.PreferenceGroup;
42import android.util.Log;
43import android.view.MenuItem;
44
45import com.android.packageinstaller.R;
46import com.android.packageinstaller.permission.model.AppPermissionGroup;
47import com.android.packageinstaller.permission.model.AppPermissions;
48import com.android.packageinstaller.permission.utils.Utils;
49
50import java.util.ArrayList;
51import java.util.Collections;
52import java.util.Comparator;
53
54public final class AllAppPermissionsFragment extends SettingsWithHeader {
55
56    private static final String LOG_TAG = "AllAppPermissionsFragment";
57
58    private static final String KEY_OTHER = "other_perms";
59
60    private PackageInfo mPackageInfo;
61
62    private AppPermissions mAppPermissions;
63
64    public static AllAppPermissionsFragment newInstance(String packageName) {
65        AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
66        Bundle arguments = new Bundle();
67        arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
68        instance.setArguments(arguments);
69        return instance;
70    }
71
72    @Override
73    public void onCreate(Bundle savedInstanceState) {
74        super.onCreate(savedInstanceState);
75        setHasOptionsMenu(true);
76        final ActionBar ab = getActivity().getActionBar();
77        if (ab != null) {
78            ab.setTitle(R.string.all_permissions);
79            ab.setDisplayHomeAsUpEnabled(true);
80        }
81
82        String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
83        try {
84            mPackageInfo = getActivity().getPackageManager().getPackageInfo(pkg,
85                    PackageManager.GET_PERMISSIONS);
86        } catch (NameNotFoundException e) {
87            getActivity().finish();
88        }
89
90        mAppPermissions = new AppPermissions(getActivity(), mPackageInfo, null, false,
91                new Runnable() {
92            @Override
93            public void run() {
94                getActivity().finish();
95            }
96        });
97    }
98
99    @Override
100    public void onResume() {
101        super.onResume();
102        updateUi();
103    }
104
105    @Override
106    public boolean onOptionsItemSelected(MenuItem item) {
107        switch (item.getItemId()) {
108            case android.R.id.home: {
109                getFragmentManager().popBackStack();
110                return true;
111            }
112        }
113        return super.onOptionsItemSelected(item);
114    }
115
116    private PreferenceGroup getOtherGroup() {
117        PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
118        if (otherGroup == null) {
119            otherGroup = new PreferenceCategory(getPreferenceManager().getContext());
120            otherGroup.setKey(KEY_OTHER);
121            otherGroup.setTitle(getString(R.string.other_permissions));
122            getPreferenceScreen().addPreference(otherGroup);
123        }
124        return otherGroup;
125    }
126
127    private void updateUi() {
128        getPreferenceScreen().removeAll();
129
130        ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
131        PackageManager pm = getActivity().getPackageManager();
132
133        ApplicationInfo appInfo = mPackageInfo.applicationInfo;
134        final Drawable icon = appInfo.loadIcon(pm);
135        final CharSequence label = appInfo.loadLabel(pm);
136        Intent infoIntent = null;
137        if (!getActivity().getIntent().getBooleanExtra(
138                AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
139            infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
140                    .setData(Uri.fromParts("package", mPackageInfo.packageName, null));
141        }
142        setHeader(icon, label, infoIntent, null);
143
144        if (mPackageInfo.requestedPermissions != null) {
145            for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) {
146                PermissionInfo perm;
147                try {
148                    perm = pm.getPermissionInfo(mPackageInfo.requestedPermissions[i], 0);
149                } catch (NameNotFoundException e) {
150                    Log.e(LOG_TAG, "Can't get permission info for "
151                            + mPackageInfo.requestedPermissions[i], e);
152                    continue;
153                }
154
155                if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
156                        || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
157                    continue;
158                }
159
160                PermissionGroupInfo group = getGroup(perm.group, pm);
161                if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
162                    PreferenceGroup pref = findOrCreate(group != null ? group : perm, pm, prefs);
163                    pref.addPreference(getPreference(perm, group));
164                } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
165                    PreferenceGroup otherGroup = getOtherGroup();
166                    if (prefs.indexOf(otherGroup) < 0) {
167                        prefs.add(otherGroup);
168                    }
169                    getOtherGroup().addPreference(getPreference(perm, group));
170                }
171            }
172        }
173
174        // Sort an ArrayList of the groups and then set the order from the sorting.
175        Collections.sort(prefs, new Comparator<Preference>() {
176            @Override
177            public int compare(Preference lhs, Preference rhs) {
178                String lKey = lhs.getKey();
179                String rKey = rhs.getKey();
180                if (lKey.equals(KEY_OTHER)) {
181                    return 1;
182                } else if (rKey.equals(KEY_OTHER)) {
183                    return -1;
184                } else if (Utils.isModernPermissionGroup(lKey)
185                        != Utils.isModernPermissionGroup(rKey)) {
186                    return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
187                }
188                return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
189            }
190        });
191        for (int i = 0; i < prefs.size(); i++) {
192            prefs.get(i).setOrder(i);
193        }
194    }
195
196    private PermissionGroupInfo getGroup(String group, PackageManager pm) {
197        try {
198            return pm.getPermissionGroupInfo(group, 0);
199        } catch (NameNotFoundException e) {
200            return null;
201        }
202    }
203
204    private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
205            ArrayList<Preference> prefs) {
206        PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
207        if (pref == null) {
208            pref = new PreferenceCategory(getActivity());
209            pref.setKey(group.name);
210            pref.setLayoutResource(R.layout.preference_category_material);
211            pref.setTitle(group.loadLabel(pm));
212            prefs.add(pref);
213            getPreferenceScreen().addPreference(pref);
214        }
215        return pref;
216    }
217
218    private Preference getPreference(final PermissionInfo perm, final PermissionGroupInfo group) {
219        if (isMutableGranularPermission(perm.name)) {
220            return getMutablePreference(perm, group);
221        } else {
222            return getImmutablePreference(perm, group);
223        }
224    }
225
226    private Preference getMutablePreference(final PermissionInfo perm, PermissionGroupInfo group) {
227        final AppPermissionGroup permGroup = mAppPermissions.getPermissionGroup(group.name);
228        final String[] filterPermissions = new String[]{perm.name};
229
230        // TODO: No hardcoded layouts
231        SwitchPreference pref = new SwitchPreference(getPreferenceManager().getContext());
232        pref.setLayoutResource(R.layout.preference_permissions);
233        pref.setChecked(permGroup.areRuntimePermissionsGranted(filterPermissions));
234        pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group));
235        pref.setTitle(perm.loadLabel(getActivity().getPackageManager()));
236        pref.setPersistent(false);
237
238        pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
239            @Override
240            public boolean onPreferenceChange(Preference preference, Object value) {
241                if (value == Boolean.TRUE) {
242                    permGroup.grantRuntimePermissions(false, filterPermissions);
243                } else {
244                    permGroup.revokeRuntimePermissions(false, filterPermissions);
245                }
246                return true;
247            }
248        });
249
250        return pref;
251    }
252
253    private Preference getImmutablePreference(final PermissionInfo perm,
254            PermissionGroupInfo group) {
255        final PackageManager pm = getActivity().getPackageManager();
256
257        // TODO: No hardcoded layouts
258        Preference pref = new Preference(getActivity());
259        pref.setLayoutResource(R.layout.preference_permissions);
260        pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group));
261        pref.setTitle(perm.loadLabel(pm));
262        pref.setPersistent(false);
263
264        pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
265            @Override
266            public boolean onPreferenceClick(Preference preference) {
267                new AlertDialog.Builder(getActivity())
268                        .setMessage(perm.loadDescription(pm))
269                        .setPositiveButton(android.R.string.ok, null)
270                        .show();
271                return true;
272            }
273        });
274
275        return pref;
276    }
277
278    private static Drawable getTintedPermissionIcon(Context context, PermissionInfo perm,
279            PermissionGroupInfo group) {
280        final Drawable icon;
281        if (perm.icon != 0) {
282            icon = perm.loadIcon(context.getPackageManager());
283        } else if (group != null && group.icon != 0) {
284            icon = group.loadIcon(context.getPackageManager());
285        } else {
286            icon =  context.getDrawable(R.drawable.ic_perm_device_info);
287        }
288        return Utils.applyTint(context, icon, android.R.attr.colorControlNormal);
289    }
290
291    private static boolean isMutableGranularPermission(String name) {
292        if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
293            return false;
294        }
295        switch (name) {
296            case Manifest.permission.READ_CONTACTS:
297            case Manifest.permission.WRITE_CONTACTS:
298            case Manifest.permission.READ_SMS:
299            case Manifest.permission.READ_CALL_LOG:
300            case Manifest.permission.CALL_PHONE: {
301                return true;
302            }
303        }
304        return false;
305    }
306}