1 /* 2 * Copyright (C) 2017 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 18 package com.android.settings.notification; 19 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS; 21 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 22 23 import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME; 24 import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID; 25 26 import android.Manifest; 27 import android.app.Activity; 28 import android.app.NotificationManager; 29 import android.app.admin.DevicePolicyManager; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.DialogInterface; 33 import android.content.Intent; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageItemInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.ResolveInfo; 38 import android.os.Bundle; 39 import android.os.UserHandle; 40 import android.os.UserManager; 41 import android.service.notification.NotificationListenerService; 42 import android.text.TextUtils; 43 import android.util.Slog; 44 import android.view.accessibility.AccessibilityEvent; 45 import android.widget.Toast; 46 47 import androidx.annotation.Nullable; 48 49 import com.android.internal.app.AlertActivity; 50 import com.android.internal.app.AlertController; 51 import com.android.settings.R; 52 53 import java.util.List; 54 55 /** @hide */ 56 public class NotificationAccessConfirmationActivity extends Activity 57 implements DialogInterface { 58 59 private static final boolean DEBUG = false; 60 private static final String LOG_TAG = "NotificationAccessConfirmationActivity"; 61 62 private int mUserId; 63 private ComponentName mComponentName; 64 private NotificationManager mNm; 65 66 private DevicePolicyManager mDpm; 67 private UserManager mUm; 68 69 @Override onCreate(@ullable Bundle savedInstanceState)70 protected void onCreate(@Nullable Bundle savedInstanceState) { 71 super.onCreate(savedInstanceState); 72 73 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 74 75 mUm = getSystemService(UserManager.class); 76 mDpm = getSystemService(DevicePolicyManager.class); 77 78 if (mUm.isManagedProfile()) { 79 Slog.w(LOG_TAG, "Apps in the work profile do not support notification listeners"); 80 Toast.makeText(this, 81 mDpm.getResources().getString(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS, 82 () -> getString(R.string.notification_settings_work_profile)), 83 Toast.LENGTH_SHORT).show(); 84 finish(); 85 return; 86 } 87 88 mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 89 90 mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME); 91 mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL); 92 CharSequence mAppLabel; 93 94 if (mComponentName == null || mComponentName.getPackageName() == null 95 || mComponentName.flattenToString().length() 96 > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { 97 finish(); 98 return; 99 } 100 101 try { 102 ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( 103 mComponentName.getPackageName(), 0); 104 mAppLabel = applicationInfo.loadSafeLabel(getPackageManager(), 105 PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, 106 PackageItemInfo.SAFE_LABEL_FLAG_TRIM 107 | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); 108 } catch (PackageManager.NameNotFoundException e) { 109 Slog.e(LOG_TAG, "Couldn't find app with package name for " + mComponentName, e); 110 finish(); 111 return; 112 } 113 114 if (TextUtils.isEmpty(mAppLabel)) { 115 finish(); 116 return; 117 } 118 119 // Check NLS service info. 120 String requiredPermission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE; 121 Intent NLSIntent = new Intent(NotificationListenerService.SERVICE_INTERFACE); 122 List<ResolveInfo> matchedServiceList = getPackageManager().queryIntentServicesAsUser( 123 NLSIntent, /* flags */ 0, mUserId); 124 boolean hasNLSIntentFilter = false; 125 for (ResolveInfo service : matchedServiceList) { 126 if (service.serviceInfo.getComponentName().equals(mComponentName)) { 127 if (!requiredPermission.equals(service.serviceInfo.permission)) { 128 Slog.e(LOG_TAG, "Service " + mComponentName + " lacks permission " 129 + requiredPermission); 130 finish(); 131 return; 132 } 133 hasNLSIntentFilter = true; 134 break; 135 } 136 } 137 if (!hasNLSIntentFilter) { 138 Slog.e(LOG_TAG, "Service " + mComponentName + " lacks an intent-filter action " 139 + "for android.service.notification.NotificationListenerService."); 140 finish(); 141 return; 142 } 143 144 AlertController.AlertParams p = new AlertController.AlertParams(this); 145 p.mTitle = getString( 146 R.string.notification_listener_security_warning_title, 147 mAppLabel); 148 p.mMessage = getString( 149 R.string.notification_listener_security_warning_summary, 150 mAppLabel); 151 p.mPositiveButtonText = getString(R.string.allow); 152 p.mPositiveButtonListener = (a, b) -> onAllow(); 153 p.mNegativeButtonText = getString(R.string.deny); 154 p.mNegativeButtonListener = (a, b) -> cancel(); 155 AlertController 156 .create(this, this, getWindow()) 157 .installContent(p); 158 // Consistent with the permission dialog 159 // Used instead of p.mCancelable as that is only honored for AlertDialog 160 getWindow().setCloseOnTouchOutside(false); 161 } 162 onAllow()163 private void onAllow() { 164 mNm.setNotificationListenerAccessGranted(mComponentName, true); 165 166 finish(); 167 } 168 169 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)170 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 171 return AlertActivity.dispatchPopulateAccessibilityEvent(this, event); 172 } 173 174 @Override cancel()175 public void cancel() { 176 finish(); 177 } 178 179 @Override dismiss()180 public void dismiss() { 181 // This is called after the click, since we finish when handling the 182 // click, don't do that again here. 183 if (!isFinishing()) { 184 finish(); 185 } 186 } 187 } 188