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