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.app.ActionBar;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.Fragment;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.DialogInterface.OnClickListener;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.graphics.Color;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.Bundle;
34import android.provider.Settings;
35import android.support.v14.preference.SwitchPreference;
36import android.support.v7.preference.Preference;
37import android.support.v7.preference.Preference.OnPreferenceChangeListener;
38import android.support.v7.preference.Preference.OnPreferenceClickListener;
39import android.support.v7.preference.PreferenceScreen;
40import android.support.v7.preference.PreferenceViewHolder;
41import android.support.v7.widget.RecyclerView;
42import android.util.Log;
43import android.view.Menu;
44import android.view.MenuInflater;
45import android.view.MenuItem;
46import android.view.View;
47import android.widget.ImageView;
48import android.widget.TextView;
49import android.widget.Toast;
50
51import com.android.packageinstaller.R;
52
53import com.android.packageinstaller.permission.model.AppPermissionGroup;
54import com.android.packageinstaller.permission.model.AppPermissions;
55import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity;
56import com.android.packageinstaller.permission.utils.LocationUtils;
57import com.android.packageinstaller.permission.utils.SafetyNetLogger;
58import com.android.packageinstaller.permission.utils.Utils;
59
60import java.util.ArrayList;
61import java.util.List;
62
63public final class AppPermissionsFragment extends SettingsWithHeader
64        implements OnPreferenceChangeListener {
65
66    private static final String LOG_TAG = "ManagePermsFragment";
67
68    static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
69
70    private static final int MENU_ALL_PERMS = 0;
71
72    private List<AppPermissionGroup> mToggledGroups;
73    private AppPermissions mAppPermissions;
74    private PreferenceScreen mExtraScreen;
75
76    private boolean mHasConfirmedRevoke;
77
78    public static AppPermissionsFragment newInstance(String packageName) {
79        return setPackageName(new AppPermissionsFragment(), packageName);
80    }
81
82    private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
83        Bundle arguments = new Bundle();
84        arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
85        fragment.setArguments(arguments);
86        return fragment;
87    }
88
89    @Override
90    public void onCreate(Bundle savedInstanceState) {
91        super.onCreate(savedInstanceState);
92        setLoading(true /* loading */, false /* animate */);
93        setHasOptionsMenu(true);
94        final ActionBar ab = getActivity().getActionBar();
95        if (ab != null) {
96            ab.setDisplayHomeAsUpEnabled(true);
97        }
98
99        String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
100        Activity activity = getActivity();
101        PackageInfo packageInfo = getPackageInfo(activity, packageName);
102        if (packageInfo == null) {
103            Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
104            activity.finish();
105            return;
106        }
107
108
109        mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() {
110            @Override
111            public void run() {
112                getActivity().finish();
113            }
114        });
115
116        if (mAppPermissions.isReviewRequired()) {
117            Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
118            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
119            startActivity(intent);
120            getActivity().finish();
121            return;
122        }
123
124        loadPreferences();
125    }
126
127    @Override
128    public void onResume() {
129        super.onResume();
130        mAppPermissions.refresh();
131        loadPreferences();
132        setPreferencesCheckedState();
133    }
134
135    @Override
136    public boolean onOptionsItemSelected(MenuItem item) {
137        switch (item.getItemId()) {
138            case android.R.id.home: {
139                getActivity().finish();
140                return true;
141            }
142
143            case MENU_ALL_PERMS: {
144                Fragment frag = AllAppPermissionsFragment.newInstance(
145                        getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
146                getFragmentManager().beginTransaction()
147                        .replace(android.R.id.content, frag)
148                        .addToBackStack("AllPerms")
149                        .commit();
150                return true;
151            }
152        }
153        return super.onOptionsItemSelected(item);
154    }
155
156    @Override
157    public void onViewCreated(View view, Bundle savedInstanceState) {
158        super.onViewCreated(view, savedInstanceState);
159        if (mAppPermissions != null) {
160            bindUi(this, mAppPermissions.getPackageInfo());
161        }
162    }
163
164    @Override
165    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
166        super.onCreateOptionsMenu(menu, inflater);
167        menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
168    }
169
170    private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
171        Activity activity = fragment.getActivity();
172        PackageManager pm = activity.getPackageManager();
173        ApplicationInfo appInfo = packageInfo.applicationInfo;
174        Intent infoIntent = null;
175        if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
176            infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
177                    .setData(Uri.fromParts("package", packageInfo.packageName, null));
178        }
179
180        Drawable icon = appInfo.loadIcon(pm);
181        CharSequence label = appInfo.loadLabel(pm);
182        fragment.setHeader(icon, label, infoIntent, fragment.getString(
183                R.string.app_permissions_decor_title));
184    }
185
186    private void loadPreferences() {
187        Context context = getPreferenceManager().getContext();
188        if (context == null) {
189            return;
190        }
191
192        PreferenceScreen screen = getPreferenceScreen();
193        screen.removeAll();
194        screen.addPreference(createHeaderLineTwoPreference(context));
195
196        if (mExtraScreen != null) {
197            mExtraScreen.removeAll();
198            mExtraScreen = null;
199        }
200
201        final Preference extraPerms = new Preference(context);
202        extraPerms.setIcon(R.drawable.ic_toc);
203        extraPerms.setTitle(R.string.additional_permissions);
204
205        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
206            if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) {
207                continue;
208            }
209
210            boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
211
212            SwitchPreference preference = new SwitchPreference(context);
213            preference.setOnPreferenceChangeListener(this);
214            preference.setKey(group.getName());
215            Drawable icon = Utils.loadDrawable(context.getPackageManager(),
216                    group.getIconPkg(), group.getIconResId());
217            preference.setIcon(Utils.applyTint(getContext(), icon,
218                    android.R.attr.colorControlNormal));
219            preference.setTitle(group.getLabel());
220            if (group.isPolicyFixed()) {
221                preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
222            }
223            preference.setPersistent(false);
224            preference.setEnabled(!group.isPolicyFixed());
225            preference.setChecked(group.areRuntimePermissionsGranted());
226
227            if (isPlatform) {
228                screen.addPreference(preference);
229            } else {
230                if (mExtraScreen == null) {
231                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
232                    mExtraScreen.addPreference(createHeaderLineTwoPreference(context));
233                }
234                mExtraScreen.addPreference(preference);
235            }
236        }
237
238        if (mExtraScreen != null) {
239            extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
240                @Override
241                public boolean onPreferenceClick(Preference preference) {
242                    AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
243                    setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
244                    frag.setTargetFragment(AppPermissionsFragment.this, 0);
245                    getFragmentManager().beginTransaction()
246                            .replace(android.R.id.content, frag)
247                            .addToBackStack(null)
248                            .commit();
249                    return true;
250                }
251            });
252            int count = mExtraScreen.getPreferenceCount();
253            extraPerms.setSummary(getResources().getQuantityString(
254                    R.plurals.additional_permissions_more, count, count));
255            screen.addPreference(extraPerms);
256        }
257
258        setLoading(false /* loading */, true /* animate */);
259    }
260
261    /**
262     * Creates a heading below decor_title and above the rest of the preferences. This heading
263     * displays the app name and banner icon. It's used in both system and additional permissions
264     * fragments for each app. The styling used is the same as a leanback preference with a
265     * customized background color
266     * @param context The context the preferences created on
267     * @return The preference header to be inserted as the first preference in the list.
268     */
269    private Preference createHeaderLineTwoPreference(Context context) {
270        Preference headerLineTwo = new Preference(context) {
271            @Override
272            public void onBindViewHolder(PreferenceViewHolder holder) {
273                super.onBindViewHolder(holder);
274                holder.itemView.setBackgroundColor(
275                        getResources().getColor(R.color.lb_header_banner_color));
276            }
277        };
278        headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
279        headerLineTwo.setSelectable(false);
280        headerLineTwo.setTitle(mLabel);
281        headerLineTwo.setIcon(mIcon);
282        return headerLineTwo;
283    }
284
285    @Override
286    public boolean onPreferenceChange(final Preference preference, Object newValue) {
287        String groupName = preference.getKey();
288        final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
289
290        if (group == null) {
291            return false;
292        }
293
294        addToggledGroup(group);
295
296        if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
297            LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
298            return false;
299        }
300        if (newValue == Boolean.TRUE) {
301            group.grantRuntimePermissions(false);
302        } else {
303            final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
304            if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) {
305                new AlertDialog.Builder(getContext())
306                        .setMessage(grantedByDefault ? R.string.system_warning
307                                : R.string.old_sdk_deny_warning)
308                        .setNegativeButton(R.string.cancel, null)
309                        .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
310                                new OnClickListener() {
311                            @Override
312                            public void onClick(DialogInterface dialog, int which) {
313                                ((SwitchPreference) preference).setChecked(false);
314                                group.revokeRuntimePermissions(false);
315                                if (!grantedByDefault) {
316                                    mHasConfirmedRevoke = true;
317                                }
318                            }
319                        })
320                        .show();
321                return false;
322            } else {
323                group.revokeRuntimePermissions(false);
324            }
325        }
326
327        return true;
328    }
329
330    @Override
331    public void onPause() {
332        super.onPause();
333        logToggledGroups();
334    }
335
336    private void addToggledGroup(AppPermissionGroup group) {
337        if (mToggledGroups == null) {
338            mToggledGroups = new ArrayList<>();
339        }
340        // Double toggle is back to initial state.
341        if (mToggledGroups.contains(group)) {
342            mToggledGroups.remove(group);
343        } else {
344            mToggledGroups.add(group);
345        }
346    }
347
348    private void logToggledGroups() {
349        if (mToggledGroups != null) {
350            String packageName = mAppPermissions.getPackageInfo().packageName;
351            SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups);
352            mToggledGroups = null;
353        }
354    }
355
356    private void setPreferencesCheckedState() {
357        setPreferencesCheckedState(getPreferenceScreen());
358        if (mExtraScreen != null) {
359            setPreferencesCheckedState(mExtraScreen);
360        }
361    }
362
363    private void setPreferencesCheckedState(PreferenceScreen screen) {
364        int preferenceCount = screen.getPreferenceCount();
365        for (int i = 0; i < preferenceCount; i++) {
366            Preference preference = screen.getPreference(i);
367            if (preference instanceof SwitchPreference) {
368                SwitchPreference switchPref = (SwitchPreference) preference;
369                AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey());
370                if (group != null) {
371                    switchPref.setChecked(group.areRuntimePermissionsGranted());
372                }
373            }
374        }
375    }
376
377    private static PackageInfo getPackageInfo(Activity activity, String packageName) {
378        try {
379            return activity.getPackageManager().getPackageInfo(
380                    packageName, PackageManager.GET_PERMISSIONS);
381        } catch (PackageManager.NameNotFoundException e) {
382            Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
383            return null;
384        }
385    }
386
387    public static class AdditionalPermissionsFragment extends SettingsWithHeader {
388        AppPermissionsFragment mOuterFragment;
389
390        @Override
391        public void onCreate(Bundle savedInstanceState) {
392            mOuterFragment = (AppPermissionsFragment) getTargetFragment();
393            super.onCreate(savedInstanceState);
394            setHasOptionsMenu(true);
395        }
396
397        @Override
398        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
399            setPreferenceScreen(mOuterFragment.mExtraScreen);
400        }
401
402        @Override
403        public void onViewCreated(View view, Bundle savedInstanceState) {
404            super.onViewCreated(view, savedInstanceState);
405            String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
406            bindUi(this, getPackageInfo(getActivity(), packageName));
407        }
408
409        private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
410            Activity activity = fragment.getActivity();
411            PackageManager pm = activity.getPackageManager();
412            ApplicationInfo appInfo = packageInfo.applicationInfo;
413            Intent infoIntent = null;
414            if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
415                infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
416                        .setData(Uri.fromParts("package", packageInfo.packageName, null));
417            }
418
419            Drawable icon = appInfo.loadIcon(pm);
420            CharSequence label = appInfo.loadLabel(pm);
421            fragment.setHeader(icon, label, infoIntent, fragment.getString(
422                    R.string.additional_permissions_decor_title));
423        }
424
425        @Override
426        public boolean onOptionsItemSelected(MenuItem item) {
427            switch (item.getItemId()) {
428            case android.R.id.home:
429                getFragmentManager().popBackStack();
430                return true;
431            }
432            return super.onOptionsItemSelected(item);
433        }
434    }
435}
436