Home | History | Annotate | Download | only in ui
      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 static android.content.pm.PackageManager.PERMISSION_DENIED;
     20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
     21 
     22 import android.app.admin.DevicePolicyManager;
     23 import android.content.Intent;
     24 import android.content.pm.PackageInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.PermissionInfo;
     28 import android.content.res.Configuration;
     29 import android.content.res.Resources;
     30 import android.graphics.drawable.Icon;
     31 import android.hardware.camera2.utils.ArrayUtils;
     32 import android.os.Build;
     33 import android.os.Bundle;
     34 import android.text.Html;
     35 import android.text.Spanned;
     36 import android.util.Log;
     37 import android.view.KeyEvent;
     38 import android.view.MotionEvent;
     39 import android.view.View;
     40 import android.view.Window;
     41 import android.view.WindowManager;
     42 
     43 import com.android.packageinstaller.DeviceUtils;
     44 import com.android.packageinstaller.R;
     45 import com.android.packageinstaller.permission.model.AppPermissionGroup;
     46 import com.android.packageinstaller.permission.model.AppPermissions;
     47 import com.android.packageinstaller.permission.model.Permission;
     48 import com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl;
     49 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
     50 
     51 import java.util.ArrayList;
     52 import java.util.Arrays;
     53 import java.util.LinkedHashMap;
     54 import java.util.List;
     55 
     56 public class GrantPermissionsActivity extends OverlayTouchActivity
     57         implements GrantPermissionsViewHandler.ResultListener {
     58 
     59     private static final String LOG_TAG = "GrantPermissionsActivity";
     60 
     61     private String[] mRequestedPermissions;
     62     private int[] mGrantResults;
     63 
     64     private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>();
     65 
     66     private GrantPermissionsViewHandler mViewHandler;
     67     private AppPermissions mAppPermissions;
     68 
     69     boolean mResultSet;
     70 
     71     @Override
     72     public void onCreate(Bundle icicle) {
     73         super.onCreate(icicle);
     74         setFinishOnTouchOutside(false);
     75 
     76         setTitle(R.string.permission_request_title);
     77 
     78         if (DeviceUtils.isTelevision(this)) {
     79             mViewHandler = new com.android.packageinstaller.permission.ui.television
     80                     .GrantPermissionsViewHandlerImpl(this).setResultListener(this);
     81         } else if (DeviceUtils.isWear(this)) {
     82             mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
     83         } else {
     84             mViewHandler = new com.android.packageinstaller.permission.ui.handheld
     85                     .GrantPermissionsViewHandlerImpl(this).setResultListener(this);
     86         }
     87 
     88         mRequestedPermissions = getIntent().getStringArrayExtra(
     89                 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
     90         if (mRequestedPermissions == null) {
     91             mRequestedPermissions = new String[0];
     92         }
     93 
     94         final int requestedPermCount = mRequestedPermissions.length;
     95         mGrantResults = new int[requestedPermCount];
     96         Arrays.fill(mGrantResults, PackageManager.PERMISSION_DENIED);
     97 
     98         if (requestedPermCount == 0) {
     99             setResultAndFinish();
    100             return;
    101         }
    102 
    103         PackageInfo callingPackageInfo = getCallingPackageInfo();
    104 
    105         if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null
    106                 || callingPackageInfo.requestedPermissions.length <= 0) {
    107             setResultAndFinish();
    108             return;
    109         }
    110 
    111         // Don't allow legacy apps to request runtime permissions.
    112         if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
    113             // Returning empty arrays means a cancellation.
    114             mRequestedPermissions = new String[0];
    115             mGrantResults = new int[0];
    116             setResultAndFinish();
    117             return;
    118         }
    119 
    120         DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
    121         final int permissionPolicy = devicePolicyManager.getPermissionPolicy(null);
    122 
    123         // If calling package is null we default to deny all.
    124         updateDefaultResults(callingPackageInfo, permissionPolicy);
    125 
    126         mAppPermissions = new AppPermissions(this, callingPackageInfo, null, false,
    127                 new Runnable() {
    128                     @Override
    129                     public void run() {
    130                         setResultAndFinish();
    131                     }
    132                 });
    133 
    134         for (String requestedPermission : mRequestedPermissions) {
    135             AppPermissionGroup group = null;
    136             for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) {
    137                 if (nextGroup.hasPermission(requestedPermission)) {
    138                     group = nextGroup;
    139                     break;
    140                 }
    141             }
    142             if (group == null) {
    143                 continue;
    144             }
    145             // We allow the user to choose only non-fixed permissions. A permission
    146             // is fixed either by device policy or the user denying with prejudice.
    147             if (!group.isUserFixed() && !group.isPolicyFixed()) {
    148                 switch (permissionPolicy) {
    149                     case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {
    150                         if (!group.areRuntimePermissionsGranted()) {
    151                             group.grantRuntimePermissions(false);
    152                         }
    153                         group.setPolicyFixed();
    154                     } break;
    155 
    156                     case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: {
    157                         if (group.areRuntimePermissionsGranted()) {
    158                             group.revokeRuntimePermissions(false);
    159                         }
    160                         group.setPolicyFixed();
    161                     } break;
    162 
    163                     default: {
    164                         if (!group.areRuntimePermissionsGranted()) {
    165                             mRequestGrantPermissionGroups.put(group.getName(),
    166                                     new GroupState(group));
    167                         } else {
    168                             group.grantRuntimePermissions(false);
    169                             updateGrantResults(group);
    170                         }
    171                     } break;
    172                 }
    173             } else {
    174                 // if the permission is fixed, ensure that we return the right request result
    175                 updateGrantResults(group);
    176             }
    177         }
    178 
    179         setContentView(mViewHandler.createView());
    180 
    181         Window window = getWindow();
    182         WindowManager.LayoutParams layoutParams = window.getAttributes();
    183         mViewHandler.updateWindowAttributes(layoutParams);
    184         window.setAttributes(layoutParams);
    185 
    186         if (!showNextPermissionGroupGrantRequest()) {
    187             setResultAndFinish();
    188         }
    189     }
    190 
    191     @Override
    192     public void onConfigurationChanged(Configuration newConfig) {
    193         super.onConfigurationChanged(newConfig);
    194         // We need to relayout the window as dialog width may be
    195         // different in landscape vs portrait which affect the min
    196         // window height needed to show all content. We have to
    197         // re-add the window to force it to be resized if needed.
    198         View decor = getWindow().getDecorView();
    199         if (decor.getParent() != null) {
    200             getWindowManager().removeViewImmediate(decor);
    201             getWindowManager().addView(decor, decor.getLayoutParams());
    202             if (mViewHandler instanceof GrantPermissionsViewHandlerImpl) {
    203                 ((GrantPermissionsViewHandlerImpl) mViewHandler).onConfigurationChanged();
    204             }
    205         }
    206     }
    207 
    208     @Override
    209     public boolean dispatchTouchEvent(MotionEvent ev) {
    210         View rootView = getWindow().getDecorView();
    211         if (rootView.getTop() != 0) {
    212             // We are animating the top view, need to compensate for that in motion events.
    213             ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
    214         }
    215         return super.dispatchTouchEvent(ev);
    216     }
    217 
    218     @Override
    219     protected void onSaveInstanceState(Bundle outState) {
    220         super.onSaveInstanceState(outState);
    221         mViewHandler.saveInstanceState(outState);
    222     }
    223 
    224     @Override
    225     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    226         super.onRestoreInstanceState(savedInstanceState);
    227         mViewHandler.loadInstanceState(savedInstanceState);
    228     }
    229 
    230     private boolean showNextPermissionGroupGrantRequest() {
    231         final int groupCount = mRequestGrantPermissionGroups.size();
    232 
    233         int currentIndex = 0;
    234         for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
    235             if (groupState.mState == GroupState.STATE_UNKNOWN) {
    236                 CharSequence appLabel = mAppPermissions.getAppLabel();
    237                 Spanned message = Html.fromHtml(getString(R.string.permission_warning_template,
    238                         appLabel, groupState.mGroup.getDescription()), 0);
    239                 // Set the permission message as the title so it can be announced.
    240                 setTitle(message);
    241 
    242                 // Set the new grant view
    243                 // TODO: Use a real message for the action. We need group action APIs
    244                 Resources resources;
    245                 try {
    246                     resources = getPackageManager().getResourcesForApplication(
    247                             groupState.mGroup.getIconPkg());
    248                 } catch (NameNotFoundException e) {
    249                     // Fallback to system.
    250                     resources = Resources.getSystem();
    251                 }
    252                 int icon = groupState.mGroup.getIconResId();
    253 
    254                 mViewHandler.updateUi(groupState.mGroup.getName(), groupCount, currentIndex,
    255                         Icon.createWithResource(resources, icon), message,
    256                         groupState.mGroup.isUserSet());
    257                 return true;
    258             }
    259 
    260             currentIndex++;
    261         }
    262 
    263         return false;
    264     }
    265 
    266     @Override
    267     public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
    268         GroupState groupState = mRequestGrantPermissionGroups.get(name);
    269         if (groupState.mGroup != null) {
    270             if (granted) {
    271                 groupState.mGroup.grantRuntimePermissions(doNotAskAgain);
    272                 groupState.mState = GroupState.STATE_ALLOWED;
    273             } else {
    274                 groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);
    275                 groupState.mState = GroupState.STATE_DENIED;
    276             }
    277             updateGrantResults(groupState.mGroup);
    278         }
    279         if (!showNextPermissionGroupGrantRequest()) {
    280             setResultAndFinish();
    281         }
    282     }
    283 
    284     private void updateGrantResults(AppPermissionGroup group) {
    285         for (Permission permission : group.getPermissions()) {
    286             final int index = ArrayUtils.getArrayIndex(
    287                     mRequestedPermissions, permission.getName());
    288             if (index >= 0) {
    289                 mGrantResults[index] = permission.isGranted() ? PackageManager.PERMISSION_GRANTED
    290                         : PackageManager.PERMISSION_DENIED;
    291             }
    292         }
    293     }
    294 
    295     @Override
    296     public boolean onKeyDown(int keyCode, KeyEvent event)  {
    297         // We do not allow backing out.
    298         return keyCode == KeyEvent.KEYCODE_BACK;
    299     }
    300 
    301     @Override
    302     public boolean onKeyUp(int keyCode, KeyEvent event)  {
    303         // We do not allow backing out.
    304         return keyCode == KeyEvent.KEYCODE_BACK;
    305     }
    306 
    307     @Override
    308     public void finish() {
    309         setResultIfNeeded(RESULT_CANCELED);
    310         super.finish();
    311     }
    312 
    313     private int computePermissionGrantState(PackageInfo callingPackageInfo,
    314             String permission, int permissionPolicy) {
    315         boolean permissionRequested = false;
    316 
    317         for (int i = 0; i < callingPackageInfo.requestedPermissions.length; i++) {
    318             if (permission.equals(callingPackageInfo.requestedPermissions[i])) {
    319                 permissionRequested = true;
    320                 if ((callingPackageInfo.requestedPermissionsFlags[i]
    321                         & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
    322                     return PERMISSION_GRANTED;
    323                 }
    324                 break;
    325             }
    326         }
    327 
    328         if (!permissionRequested) {
    329             return PERMISSION_DENIED;
    330         }
    331 
    332         try {
    333             PermissionInfo pInfo = getPackageManager().getPermissionInfo(permission, 0);
    334             if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
    335                     != PermissionInfo.PROTECTION_DANGEROUS) {
    336                 return PERMISSION_DENIED;
    337             }
    338         } catch (NameNotFoundException e) {
    339             return PERMISSION_DENIED;
    340         }
    341 
    342         switch (permissionPolicy) {
    343             case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {
    344                 return PERMISSION_GRANTED;
    345             }
    346             default: {
    347                 return PERMISSION_DENIED;
    348             }
    349         }
    350     }
    351 
    352     private PackageInfo getCallingPackageInfo() {
    353         try {
    354             return getPackageManager().getPackageInfo(getCallingPackage(),
    355                     PackageManager.GET_PERMISSIONS);
    356         } catch (NameNotFoundException e) {
    357             Log.i(LOG_TAG, "No package: " + getCallingPackage(), e);
    358             return null;
    359         }
    360     }
    361 
    362     private void updateDefaultResults(PackageInfo callingPackageInfo, int permissionPolicy) {
    363         final int requestedPermCount = mRequestedPermissions.length;
    364         for (int i = 0; i < requestedPermCount; i++) {
    365             String permission = mRequestedPermissions[i];
    366             mGrantResults[i] = callingPackageInfo != null
    367                     ? computePermissionGrantState(callingPackageInfo, permission, permissionPolicy)
    368                     : PERMISSION_DENIED;
    369         }
    370     }
    371 
    372     private void setResultIfNeeded(int resultCode) {
    373         if (!mResultSet) {
    374             mResultSet = true;
    375             logRequestedPermissionGroups();
    376             Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
    377             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
    378             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults);
    379             setResult(resultCode, result);
    380         }
    381     }
    382 
    383     private void setResultAndFinish() {
    384         setResultIfNeeded(RESULT_OK);
    385         finish();
    386     }
    387 
    388     private void logRequestedPermissionGroups() {
    389         if (mRequestGrantPermissionGroups.isEmpty()) {
    390             return;
    391         }
    392 
    393         final int groupCount = mRequestGrantPermissionGroups.size();
    394         List<AppPermissionGroup> groups = new ArrayList<>(groupCount);
    395         for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
    396             groups.add(groupState.mGroup);
    397         }
    398 
    399         SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups);
    400     }
    401 
    402     private static final class GroupState {
    403         static final int STATE_UNKNOWN = 0;
    404         static final int STATE_ALLOWED = 1;
    405         static final int STATE_DENIED = 2;
    406 
    407         final AppPermissionGroup mGroup;
    408         int mState = STATE_UNKNOWN;
    409 
    410         GroupState(AppPermissionGroup group) {
    411             mGroup = group;
    412         }
    413     }
    414 }
    415