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 
17 package com.android.packageinstaller.permission.ui;
18 
19 import android.app.Activity;
20 
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.content.Intent;
24 import android.content.IntentSender;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.graphics.drawable.Drawable;
28 import android.os.Bundle;
29 import android.os.RemoteCallback;
30 import android.preference.Preference;
31 import android.preference.PreferenceCategory;
32 import android.preference.PreferenceFragment;
33 import android.preference.PreferenceGroup;
34 import android.preference.PreferenceScreen;
35 import android.preference.SwitchPreference;
36 import android.preference.TwoStatePreference;
37 import android.text.Html;
38 import android.text.SpannableString;
39 import android.text.Spanned;
40 import android.text.TextUtils;
41 import android.text.style.ForegroundColorSpan;
42 import android.util.TypedValue;
43 import android.view.View;
44 import android.widget.Button;
45 import android.widget.ImageView;
46 import android.widget.TextView;
47 import com.android.packageinstaller.DeviceUtils;
48 import com.android.packageinstaller.R;
49 import com.android.packageinstaller.permission.model.AppPermissionGroup;
50 import com.android.packageinstaller.permission.model.AppPermissions;
51 import com.android.packageinstaller.permission.utils.Utils;
52 import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment.OnActionConfirmedListener;
53 import com.android.packageinstaller.permission.ui.wear.ReviewPermissionsWearFragment;
54 
55 import java.util.List;
56 
57 public final class ReviewPermissionsActivity extends Activity
58         implements OnActionConfirmedListener {
59 
60     @Override
onCreate(Bundle savedInstanceState)61     protected void onCreate(Bundle savedInstanceState) {
62         super.onCreate(savedInstanceState);
63 
64         PackageInfo packageInfo = getTargetPackageInfo();
65         if (packageInfo == null) {
66             finish();
67             return;
68         }
69 
70         if (DeviceUtils.isWear(this)) {
71             Fragment fragment = ReviewPermissionsWearFragment.newInstance(packageInfo);
72             getFragmentManager().beginTransaction()
73                     .replace(android.R.id.content, fragment).commit();
74         } else {
75             setContentView(R.layout.review_permissions);
76             if (getFragmentManager().findFragmentById(R.id.preferences_frame) == null) {
77                 getFragmentManager().beginTransaction().add(R.id.preferences_frame,
78                         ReviewPermissionsFragment.newInstance(packageInfo)).commit();
79             }
80         }
81     }
82 
83     @Override
onActionConfirmed(String action)84     public void onActionConfirmed(String action) {
85         Fragment fragment = getFragmentManager().findFragmentById(R.id.preferences_frame);
86         if (fragment instanceof OnActionConfirmedListener) {
87             ((OnActionConfirmedListener) fragment).onActionConfirmed(action);
88         }
89     }
90 
getTargetPackageInfo()91     private PackageInfo getTargetPackageInfo() {
92         String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
93         if (TextUtils.isEmpty(packageName)) {
94             return null;
95         }
96         try {
97             return getPackageManager().getPackageInfo(packageName,
98                     PackageManager.GET_PERMISSIONS);
99         } catch (PackageManager.NameNotFoundException e) {
100             return null;
101         }
102     }
103 
104     public static final class ReviewPermissionsFragment extends PreferenceFragment
105             implements View.OnClickListener, Preference.OnPreferenceChangeListener,
106             ConfirmActionDialogFragment.OnActionConfirmedListener {
107         public static final String EXTRA_PACKAGE_INFO =
108                 "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO";
109 
110         private AppPermissions mAppPermissions;
111 
112         private Button mContinueButton;
113         private Button mCancelButton;
114         private Button mMoreInfoButton;
115 
116         private PreferenceCategory mNewPermissionsCategory;
117 
118         private boolean mHasConfirmedRevoke;
119 
newInstance(PackageInfo packageInfo)120         public static ReviewPermissionsFragment newInstance(PackageInfo packageInfo) {
121             Bundle arguments = new Bundle();
122             arguments.putParcelable(ReviewPermissionsFragment.EXTRA_PACKAGE_INFO, packageInfo);
123             ReviewPermissionsFragment instance = new ReviewPermissionsFragment();
124             instance.setArguments(arguments);
125             instance.setRetainInstance(true);
126             return instance;
127         }
128 
129         @Override
onCreate(Bundle savedInstanceState)130         public void onCreate(Bundle savedInstanceState) {
131             super.onCreate(savedInstanceState);
132 
133             Activity activity = getActivity();
134             if (activity == null) {
135                 return;
136             }
137 
138             PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO);
139             if (packageInfo == null) {
140                 activity.finish();
141                 return;
142             }
143 
144             mAppPermissions = new AppPermissions(activity, packageInfo, null, false,
145                     new Runnable() {
146                         @Override
147                         public void run() {
148                             getActivity().finish();
149                         }
150                     });
151 
152             if (mAppPermissions.getPermissionGroups().isEmpty()) {
153                 activity.finish();
154                 return;
155             }
156 
157             boolean reviewRequired = false;
158             for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
159                 if (group.isReviewRequired()) {
160                     reviewRequired = true;
161                     break;
162                 }
163             }
164 
165             if (!reviewRequired) {
166                 activity.finish();
167             }
168         }
169 
170         @Override
onViewCreated(View view, Bundle savedInstanceState)171         public void onViewCreated(View view, Bundle savedInstanceState) {
172             super.onViewCreated(view, savedInstanceState);
173             bindUi();
174         }
175 
176         @Override
onResume()177         public void onResume() {
178             super.onResume();
179             mAppPermissions.refresh();
180             loadPreferences();
181         }
182 
183         @Override
onClick(View view)184         public void onClick(View view) {
185             Activity activity = getActivity();
186             if (activity == null) {
187                 return;
188             }
189             if (view == mContinueButton) {
190                 confirmPermissionsReview();
191                 executeCallback(true);
192             } else if (view == mCancelButton) {
193                 executeCallback(false);
194                 activity.setResult(Activity.RESULT_CANCELED);
195             } else if (view == mMoreInfoButton) {
196                 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
197                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME,
198                         mAppPermissions.getPackageInfo().packageName);
199                 intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
200                 getActivity().startActivity(intent);
201             }
202             activity.finish();
203         }
204 
205         @Override
onPreferenceChange(Preference preference, Object newValue)206         public boolean onPreferenceChange(Preference preference, Object newValue) {
207             if (mHasConfirmedRevoke) {
208                 return true;
209             }
210             if (preference instanceof SwitchPreference) {
211                 SwitchPreference switchPreference = (SwitchPreference) preference;
212                 if (switchPreference.isChecked()) {
213                     showWarnRevokeDialog(switchPreference.getKey());
214                 } else {
215                     return true;
216                 }
217             }
218             return false;
219         }
220 
221         @Override
onActionConfirmed(String action)222         public void onActionConfirmed(String action) {
223             Preference preference = getPreferenceManager().findPreference(action);
224             if (preference instanceof SwitchPreference) {
225                 SwitchPreference switchPreference = (SwitchPreference) preference;
226                 switchPreference.setChecked(false);
227                 mHasConfirmedRevoke = true;
228             }
229         }
230 
showWarnRevokeDialog(final String groupName)231         private void showWarnRevokeDialog(final String groupName) {
232             DialogFragment fragment = ConfirmActionDialogFragment.newInstance(
233                     getString(R.string.old_sdk_deny_warning), groupName);
234             fragment.show(getFragmentManager(), fragment.getClass().getName());
235         }
236 
confirmPermissionsReview()237         private void confirmPermissionsReview() {
238             PreferenceGroup preferenceGroup = mNewPermissionsCategory != null
239                 ? mNewPermissionsCategory : getPreferenceScreen();
240 
241             final int preferenceCount = preferenceGroup.getPreferenceCount();
242             for (int i = 0; i < preferenceCount; i++) {
243                 Preference preference = preferenceGroup.getPreference(i);
244                 if (preference instanceof TwoStatePreference) {
245                     TwoStatePreference twoStatePreference = (TwoStatePreference) preference;
246                     String groupName = preference.getKey();
247                     AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
248                     if (twoStatePreference.isChecked()) {
249                         group.grantRuntimePermissions(false);
250                     } else {
251                         group.revokeRuntimePermissions(false);
252                     }
253                     group.resetReviewRequired();
254                 }
255             }
256         }
257 
bindUi()258         private void bindUi() {
259             Activity activity = getActivity();
260             if (activity == null) {
261                 return;
262             }
263 
264             // Set icon
265             Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon(
266                     activity.getPackageManager());
267             ImageView iconView = (ImageView) activity.findViewById(R.id.app_icon);
268             iconView.setImageDrawable(icon);
269 
270             // Set message
271             final int labelTemplateResId = isPackageUpdated()
272                     ?  R.string.permission_review_title_template_update
273                     :  R.string.permission_review_title_template_install;
274             Spanned message = Html.fromHtml(getString(labelTemplateResId,
275                     mAppPermissions.getAppLabel()), 0);
276 
277             // Set the permission message as the title so it can be announced.
278             activity.setTitle(message.toString());
279 
280             // Color the app name.
281             TextView permissionsMessageView = (TextView) activity.findViewById(
282                     R.id.permissions_message);
283             permissionsMessageView.setText(message);
284 
285             mContinueButton = (Button) getActivity().findViewById(R.id.continue_button);
286             mContinueButton.setOnClickListener(this);
287 
288             mCancelButton = (Button) getActivity().findViewById(R.id.cancel_button);
289             mCancelButton.setOnClickListener(this);
290 
291             mMoreInfoButton = (Button)  getActivity().findViewById(
292                     R.id.permission_more_info_button);
293             mMoreInfoButton.setOnClickListener(this);
294         }
295 
loadPreferences()296         private void loadPreferences() {
297             Activity activity = getActivity();
298             if (activity == null) {
299                 return;
300             }
301 
302             PreferenceScreen screen = getPreferenceScreen();
303             if (screen == null) {
304                 screen = getPreferenceManager().createPreferenceScreen(getActivity());
305                 setPreferenceScreen(screen);
306             } else {
307                 screen.removeAll();
308             }
309 
310             PreferenceGroup currentPermissionsCategory = null;
311             PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory;
312             mNewPermissionsCategory = null;
313 
314             final boolean isPackageUpdated = isPackageUpdated();
315 
316             for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
317                 if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)
318                         || !Utils.OS_PKG.equals(group.getDeclaringPackage())) {
319                     continue;
320                 }
321 
322                 // TODO: Sort permissions - platform first then third-party ones
323 
324                 final SwitchPreference preference;
325                 Preference cachedPreference = oldNewPermissionsCategory != null
326                         ? oldNewPermissionsCategory.findPreference(group.getName()) : null;
327                 if (cachedPreference instanceof SwitchPreference) {
328                     preference = (SwitchPreference) cachedPreference;
329                 } else {
330                     preference = new SwitchPreference(getActivity());
331 
332                     preference.setKey(group.getName());
333                     Drawable icon = Utils.loadDrawable(activity.getPackageManager(),
334                             group.getIconPkg(), group.getIconResId());
335                     preference.setIcon(Utils.applyTint(getContext(), icon,
336                             android.R.attr.colorControlNormal));
337                     preference.setTitle(group.getLabel());
338                     preference.setSummary(group.getDescription());
339                     preference.setPersistent(false);
340 
341                     preference.setOnPreferenceChangeListener(this);
342                 }
343 
344                 preference.setChecked(group.areRuntimePermissionsGranted());
345 
346                 // Mutable state
347                 if (group.isPolicyFixed()) {
348                     preference.setEnabled(false);
349                     preference.setSummary(getString(
350                             R.string.permission_summary_enforced_by_policy));
351                 } else {
352                     preference.setEnabled(true);
353                 }
354 
355                 if (group.isReviewRequired()) {
356                     if (!isPackageUpdated) {
357                         screen.addPreference(preference);
358                     } else {
359                         if (mNewPermissionsCategory == null) {
360                             mNewPermissionsCategory = new PreferenceCategory(activity);
361                             mNewPermissionsCategory.setTitle(R.string.new_permissions_category);
362                             mNewPermissionsCategory.setOrder(1);
363                             screen.addPreference(mNewPermissionsCategory);
364                         }
365                         mNewPermissionsCategory.addPreference(preference);
366                     }
367                 } else {
368                     if (currentPermissionsCategory == null) {
369                         currentPermissionsCategory = new PreferenceCategory(activity);
370                         currentPermissionsCategory.setTitle(R.string.current_permissions_category);
371                         currentPermissionsCategory.setOrder(2);
372                         screen.addPreference(currentPermissionsCategory);
373                     }
374                     currentPermissionsCategory.addPreference(preference);
375                 }
376             }
377         }
378 
isPackageUpdated()379         private boolean isPackageUpdated() {
380             List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
381             final int groupCount = groups.size();
382             for (int i = 0; i < groupCount; i++) {
383                 AppPermissionGroup group = groups.get(i);
384                 if (!group.isReviewRequired()) {
385                     return true;
386                 }
387             }
388             return false;
389         }
390 
executeCallback(boolean success)391         private void executeCallback(boolean success) {
392             Activity activity = getActivity();
393             if (activity == null) {
394                 return;
395             }
396             if (success) {
397                 IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
398                 if (intent != null) {
399                     try {
400                         int flagMask = 0;
401                         int flagValues = 0;
402                         if (activity.getIntent().getBooleanExtra(
403                                 Intent.EXTRA_RESULT_NEEDED, false)) {
404                             flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
405                             flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
406                         }
407                         activity.startIntentSenderForResult(intent, -1, null,
408                                 flagMask, flagValues, 0);
409                     } catch (IntentSender.SendIntentException e) {
410                         /* ignore */
411                     }
412                     return;
413                 }
414             }
415             RemoteCallback callback = activity.getIntent().getParcelableExtra(
416                     Intent.EXTRA_REMOTE_CALLBACK);
417             if (callback != null) {
418                 Bundle result = new Bundle();
419                 result.putBoolean(Intent.EXTRA_RETURN_RESULT, success);
420                 callback.sendResult(result);
421             }
422         }
423     }
424 }
425