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