1 /* 2 * Copyright (C) 2016 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.settings.notification; 18 19 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 20 import static android.app.NotificationManager.IMPORTANCE_LOW; 21 import static android.app.NotificationManager.IMPORTANCE_NONE; 22 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 23 24 import com.android.internal.widget.LockPatternUtils; 25 import com.android.settings.R; 26 import com.android.settings.SettingsPreferenceFragment; 27 import com.android.settings.applications.AppInfoBase; 28 import com.android.settings.applications.LayoutPreference; 29 import com.android.settings.widget.SwitchBar; 30 import com.android.settingslib.RestrictedLockUtils; 31 import com.android.settingslib.RestrictedSwitchPreference; 32 import com.android.settingslib.widget.FooterPreference; 33 34 import android.app.Notification; 35 import android.app.NotificationChannel; 36 import android.app.NotificationManager; 37 import android.app.admin.DevicePolicyManager; 38 import android.content.BroadcastReceiver; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.content.pm.ActivityInfo; 43 import android.content.pm.ApplicationInfo; 44 import android.content.pm.PackageInfo; 45 import android.content.pm.PackageManager; 46 import android.content.pm.PackageManager.NameNotFoundException; 47 import android.content.pm.ResolveInfo; 48 import android.content.pm.UserInfo; 49 import android.net.Uri; 50 import android.os.Bundle; 51 import android.os.UserHandle; 52 import android.os.UserManager; 53 import android.provider.Settings; 54 import android.service.notification.NotificationListenerService; 55 import android.support.v7.preference.DropDownPreference; 56 import android.support.v7.preference.Preference; 57 import android.support.v7.preference.PreferenceGroup; 58 import android.text.TextUtils; 59 import android.util.ArrayMap; 60 import android.util.Log; 61 import android.widget.Toast; 62 63 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 64 65 import java.util.ArrayList; 66 import java.util.List; 67 68 abstract public class NotificationSettingsBase extends SettingsPreferenceFragment { 69 private static final String TAG = "NotifiSettingsBase"; 70 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 71 72 private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT 73 = new Intent(Intent.ACTION_MAIN) 74 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES); 75 76 protected static final int ORDER_FIRST = -500; 77 protected static final int ORDER_LAST = 1000; 78 79 protected static final String KEY_APP_LINK = "app_link"; 80 protected static final String KEY_HEADER = "header"; 81 protected static final String KEY_BLOCK = "block"; 82 protected static final String KEY_BADGE = "badge"; 83 protected static final String KEY_BYPASS_DND = "bypass_dnd"; 84 protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override"; 85 protected static final String KEY_BLOCKED_DESC = "block_desc"; 86 protected static final String KEY_ALLOW_SOUND = "allow_sound"; 87 88 protected PackageManager mPm; 89 protected UserManager mUm; 90 protected NotificationBackend mBackend = new NotificationBackend(); 91 protected LockPatternUtils mLockPatternUtils; 92 protected NotificationManager mNm; 93 protected Context mContext; 94 protected boolean mCreated; 95 protected int mUid; 96 protected int mUserId; 97 protected String mPkg; 98 protected PackageInfo mPkgInfo; 99 protected RestrictedSwitchPreference mBadge; 100 protected RestrictedSwitchPreference mPriority; 101 protected RestrictedDropDownPreference mVisibilityOverride; 102 protected RestrictedSwitchPreference mImportanceToggle; 103 protected LayoutPreference mBlockBar; 104 protected SwitchBar mSwitchBar; 105 protected FooterPreference mBlockedDesc; 106 protected Preference mAppLink; 107 108 protected EnforcedAdmin mSuspendedAppsAdmin; 109 protected boolean mDndVisualEffectsSuppressed; 110 111 protected NotificationChannel mChannel; 112 protected NotificationBackend.AppRow mAppRow; 113 protected boolean mShowLegacyChannelConfig = false; 114 115 protected boolean mListeningToPackageRemove; 116 117 @Override onActivityCreated(Bundle savedInstanceState)118 public void onActivityCreated(Bundle savedInstanceState) { 119 super.onActivityCreated(savedInstanceState); 120 if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated); 121 if (mCreated) { 122 Log.w(TAG, "onActivityCreated: ignoring duplicate call"); 123 return; 124 } 125 mCreated = true; 126 } 127 128 @Override onCreate(Bundle savedInstanceState)129 public void onCreate(Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 mContext = getActivity(); 132 Intent intent = getActivity().getIntent(); 133 Bundle args = getArguments(); 134 if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent); 135 if (intent == null && args == null) { 136 Log.w(TAG, "No intent"); 137 toastAndFinish(); 138 return; 139 } 140 141 mPm = getPackageManager(); 142 mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 143 mNm = NotificationManager.from(mContext); 144 145 mPkg = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_NAME) 146 ? args.getString(AppInfoBase.ARG_PACKAGE_NAME) 147 : intent.getStringExtra(Settings.EXTRA_APP_PACKAGE); 148 mUid = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_UID) 149 ? args.getInt(AppInfoBase.ARG_PACKAGE_UID) 150 : intent.getIntExtra(Settings.EXTRA_APP_UID, -1); 151 152 if (mUid < 0) { 153 try { 154 mUid = mPm.getPackageUid(mPkg, 0); 155 } catch (NameNotFoundException e) { 156 } 157 } 158 159 mPkgInfo = findPackageInfo(mPkg, mUid); 160 161 if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { 162 Log.w(TAG, "Missing package or uid or packageinfo"); 163 toastAndFinish(); 164 return; 165 } 166 167 mUserId = UserHandle.getUserId(mUid); 168 startListeningToPackageRemove(); 169 } 170 171 @Override onDestroy()172 public void onDestroy() { 173 stopListeningToPackageRemove(); 174 super.onDestroy(); 175 } 176 177 @Override onResume()178 public void onResume() { 179 super.onResume(); 180 if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { 181 Log.w(TAG, "Missing package or uid or packageinfo"); 182 finish(); 183 return; 184 } 185 mAppRow = mBackend.loadAppRow(mContext, mPm, mPkgInfo); 186 Bundle args = getArguments(); 187 mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ? 188 mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null; 189 190 mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( 191 mContext, mPkg, mUserId); 192 NotificationManager.Policy policy = mNm.getNotificationPolicy(); 193 mDndVisualEffectsSuppressed = policy == null ? false : policy.suppressedVisualEffects != 0; 194 195 mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( 196 mContext, mPkg, mUserId); 197 } 198 setVisible(Preference p, boolean visible)199 protected void setVisible(Preference p, boolean visible) { 200 setVisible(getPreferenceScreen(), p, visible); 201 } 202 setVisible(PreferenceGroup parent, Preference p, boolean visible)203 protected void setVisible(PreferenceGroup parent, Preference p, boolean visible) { 204 final boolean isVisible = parent.findPreference(p.getKey()) != null; 205 if (isVisible == visible) return; 206 if (visible) { 207 parent.addPreference(p); 208 } else { 209 parent.removePreference(p); 210 } 211 } 212 toastAndFinish()213 protected void toastAndFinish() { 214 Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show(); 215 getActivity().finish(); 216 } 217 queryNotificationConfigActivities()218 private List<ResolveInfo> queryNotificationConfigActivities() { 219 if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " 220 + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); 221 final List<ResolveInfo> resolveInfos = mPm.queryIntentActivities( 222 APP_NOTIFICATION_PREFS_CATEGORY_INTENT, 223 0 //PackageManager.MATCH_DEFAULT_ONLY 224 ); 225 return resolveInfos; 226 } 227 collectConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows)228 protected void collectConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows) { 229 final List<ResolveInfo> resolveInfos = queryNotificationConfigActivities(); 230 applyConfigActivities(rows, resolveInfos); 231 } 232 applyConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows, List<ResolveInfo> resolveInfos)233 private void applyConfigActivities(ArrayMap<String, NotificationBackend.AppRow> rows, 234 List<ResolveInfo> resolveInfos) { 235 if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" 236 + (resolveInfos.size() == 0 ? " ;_;" : "")); 237 for (ResolveInfo ri : resolveInfos) { 238 final ActivityInfo activityInfo = ri.activityInfo; 239 final ApplicationInfo appInfo = activityInfo.applicationInfo; 240 final NotificationBackend.AppRow row = rows.get(appInfo.packageName); 241 if (row == null) { 242 if (DEBUG) Log.v(TAG, "Ignoring notification preference activity (" 243 + activityInfo.name + ") for unknown package " 244 + activityInfo.packageName); 245 continue; 246 } 247 if (row.settingsIntent != null) { 248 if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity (" 249 + activityInfo.name + ") for package " 250 + activityInfo.packageName); 251 continue; 252 } 253 row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT) 254 .setClassName(activityInfo.packageName, activityInfo.name); 255 if (mChannel != null) { 256 row.settingsIntent.putExtra(Notification.EXTRA_CHANNEL_ID, mChannel.getId()); 257 } 258 } 259 } 260 findPackageInfo(String pkg, int uid)261 private PackageInfo findPackageInfo(String pkg, int uid) { 262 if (pkg == null || uid < 0) { 263 return null; 264 } 265 final String[] packages = mPm.getPackagesForUid(uid); 266 if (packages != null && pkg != null) { 267 final int N = packages.length; 268 for (int i = 0; i < N; i++) { 269 final String p = packages[i]; 270 if (pkg.equals(p)) { 271 try { 272 return mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); 273 } catch (NameNotFoundException e) { 274 Log.w(TAG, "Failed to load package " + pkg, e); 275 } 276 } 277 } 278 } 279 return null; 280 } 281 addAppLinkPref()282 protected void addAppLinkPref() { 283 if (mAppRow.settingsIntent != null && mAppLink == null) { 284 addPreferencesFromResource(R.xml.inapp_notification_settings); 285 mAppLink = (Preference) findPreference(KEY_APP_LINK); 286 mAppLink.setIntent(mAppRow.settingsIntent); 287 } 288 } 289 populateDefaultChannelPrefs()290 protected void populateDefaultChannelPrefs() { 291 if (mPkgInfo != null && mChannel != null) { 292 addPreferencesFromResource(R.xml.legacy_channel_notification_settings); 293 setupPriorityPref(mChannel.canBypassDnd()); 294 setupVisOverridePref(mChannel.getLockscreenVisibility()); 295 setupImportanceToggle(); 296 setupBadge(); 297 } 298 mSwitchBar.setChecked(!mAppRow.banned 299 && mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE); 300 } 301 setupBadge()302 abstract void setupBadge(); 303 updateDependents(boolean banned)304 abstract void updateDependents(boolean banned); 305 306 // 'allow sound' setupImportanceToggle()307 private void setupImportanceToggle() { 308 mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND); 309 mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin); 310 mImportanceToggle.setEnabled(isChannelConfigurable(mChannel) 311 && !mImportanceToggle.isDisabledByAdmin()); 312 mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT 313 || mChannel.getImportance() == IMPORTANCE_UNSPECIFIED); 314 mImportanceToggle.setOnPreferenceChangeListener( 315 new Preference.OnPreferenceChangeListener() { 316 @Override 317 public boolean onPreferenceChange(Preference preference, Object newValue) { 318 final int importance = 319 ((Boolean) newValue ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_LOW); 320 mChannel.setImportance(importance); 321 mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 322 mBackend.updateChannel(mPkg, mUid, mChannel); 323 updateDependents(mChannel.getImportance() == IMPORTANCE_NONE); 324 return true; 325 } 326 }); 327 } 328 setupPriorityPref(boolean priority)329 protected void setupPriorityPref(boolean priority) { 330 mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND); 331 mPriority.setDisabledByAdmin(mSuspendedAppsAdmin); 332 mPriority.setEnabled(isChannelConfigurable(mChannel) && !mPriority.isDisabledByAdmin()); 333 mPriority.setChecked(priority); 334 mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 335 @Override 336 public boolean onPreferenceChange(Preference preference, Object newValue) { 337 final boolean bypassZenMode = (Boolean) newValue; 338 mChannel.setBypassDnd(bypassZenMode); 339 mChannel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 340 mBackend.updateChannel(mPkg, mUid, mChannel); 341 return true; 342 } 343 }); 344 } 345 setupVisOverridePref(int sensitive)346 protected void setupVisOverridePref(int sensitive) { 347 mVisibilityOverride = 348 (RestrictedDropDownPreference) findPreference(KEY_VISIBILITY_OVERRIDE); 349 ArrayList<CharSequence> entries = new ArrayList<>(); 350 ArrayList<CharSequence> values = new ArrayList<>(); 351 352 mVisibilityOverride.clearRestrictedItems(); 353 if (getLockscreenNotificationsEnabled() && getLockscreenAllowPrivateNotifications()) { 354 final String summaryShowEntry = 355 getString(R.string.lock_screen_notifications_summary_show); 356 final String summaryShowEntryValue = 357 Integer.toString(NotificationManager.VISIBILITY_NO_OVERRIDE); 358 entries.add(summaryShowEntry); 359 values.add(summaryShowEntryValue); 360 setRestrictedIfNotificationFeaturesDisabled(summaryShowEntry, summaryShowEntryValue, 361 DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS 362 | DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); 363 } 364 365 final String summaryHideEntry = getString(R.string.lock_screen_notifications_summary_hide); 366 final String summaryHideEntryValue = Integer.toString(Notification.VISIBILITY_PRIVATE); 367 entries.add(summaryHideEntry); 368 values.add(summaryHideEntryValue); 369 setRestrictedIfNotificationFeaturesDisabled(summaryHideEntry, summaryHideEntryValue, 370 DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); 371 entries.add(getString(R.string.lock_screen_notifications_summary_disable)); 372 values.add(Integer.toString(Notification.VISIBILITY_SECRET)); 373 mVisibilityOverride.setEntries(entries.toArray(new CharSequence[entries.size()])); 374 mVisibilityOverride.setEntryValues(values.toArray(new CharSequence[values.size()])); 375 376 if (sensitive == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { 377 mVisibilityOverride.setValue(Integer.toString(getGlobalVisibility())); 378 } else { 379 mVisibilityOverride.setValue(Integer.toString(sensitive)); 380 } 381 mVisibilityOverride.setSummary("%s"); 382 383 mVisibilityOverride.setOnPreferenceChangeListener( 384 new Preference.OnPreferenceChangeListener() { 385 @Override 386 public boolean onPreferenceChange(Preference preference, Object newValue) { 387 int sensitive = Integer.parseInt((String) newValue); 388 if (sensitive == getGlobalVisibility()) { 389 sensitive = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; 390 } 391 mChannel.setLockscreenVisibility(sensitive); 392 mChannel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 393 mBackend.updateChannel(mPkg, mUid, mChannel); 394 return true; 395 } 396 }); 397 mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin); 398 } 399 setupBlockDesc(int summaryResId)400 protected void setupBlockDesc(int summaryResId) { 401 mBlockedDesc = (FooterPreference) getPreferenceScreen().findPreference( 402 KEY_BLOCKED_DESC); 403 mBlockedDesc = new FooterPreference(getPrefContext()); 404 mBlockedDesc.setSelectable(false); 405 mBlockedDesc.setTitle(summaryResId); 406 mBlockedDesc.setEnabled(false); 407 mBlockedDesc.setOrder(50); 408 getPreferenceScreen().addPreference(mBlockedDesc); 409 } 410 checkCanBeVisible(int minImportanceVisible)411 protected boolean checkCanBeVisible(int minImportanceVisible) { 412 int importance = mChannel.getImportance(); 413 if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) { 414 return true; 415 } 416 return importance >= minImportanceVisible; 417 } 418 setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures)419 private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, 420 CharSequence entryValue, int keyguardNotificationFeatures) { 421 RestrictedLockUtils.EnforcedAdmin admin = 422 RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( 423 mContext, keyguardNotificationFeatures, mUserId); 424 if (admin != null) { 425 RestrictedDropDownPreference.RestrictedItem item = 426 new RestrictedDropDownPreference.RestrictedItem(entry, entryValue, admin); 427 mVisibilityOverride.addRestrictedItem(item); 428 } 429 } 430 getGlobalVisibility()431 private int getGlobalVisibility() { 432 int globalVis = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; 433 if (!getLockscreenNotificationsEnabled()) { 434 globalVis = Notification.VISIBILITY_SECRET; 435 } else if (!getLockscreenAllowPrivateNotifications()) { 436 globalVis = Notification.VISIBILITY_PRIVATE; 437 } 438 return globalVis; 439 } 440 getLockscreenNotificationsEnabled()441 private boolean getLockscreenNotificationsEnabled() { 442 return Settings.Secure.getInt(getContentResolver(), 443 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; 444 } 445 getLockscreenAllowPrivateNotifications()446 private boolean getLockscreenAllowPrivateNotifications() { 447 return Settings.Secure.getInt(getContentResolver(), 448 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; 449 } 450 isLockScreenSecure()451 protected boolean isLockScreenSecure() { 452 if (mLockPatternUtils == null) { 453 mLockPatternUtils = new LockPatternUtils(getActivity()); 454 } 455 boolean lockscreenSecure = mLockPatternUtils.isSecure(UserHandle.myUserId()); 456 UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId()); 457 if (parentUser != null){ 458 lockscreenSecure |= mLockPatternUtils.isSecure(parentUser.id); 459 } 460 461 return lockscreenSecure; 462 } 463 isChannelConfigurable(NotificationChannel channel)464 protected boolean isChannelConfigurable(NotificationChannel channel) { 465 return !channel.getId().equals(mAppRow.lockedChannelId); 466 } 467 isChannelBlockable(boolean systemApp, NotificationChannel channel)468 protected boolean isChannelBlockable(boolean systemApp, NotificationChannel channel) { 469 if (!mAppRow.systemApp) { 470 return true; 471 } 472 473 return channel.isBlockableSystem() 474 || channel.getImportance() == NotificationManager.IMPORTANCE_NONE; 475 } 476 startListeningToPackageRemove()477 protected void startListeningToPackageRemove() { 478 if (mListeningToPackageRemove) { 479 return; 480 } 481 mListeningToPackageRemove = true; 482 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 483 filter.addDataScheme("package"); 484 getContext().registerReceiver(mPackageRemovedReceiver, filter); 485 } 486 stopListeningToPackageRemove()487 protected void stopListeningToPackageRemove() { 488 if (!mListeningToPackageRemove) { 489 return; 490 } 491 mListeningToPackageRemove = false; 492 getContext().unregisterReceiver(mPackageRemovedReceiver); 493 } 494 onPackageRemoved()495 protected void onPackageRemoved() { 496 getActivity().finishAndRemoveTask(); 497 } 498 499 protected final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { 500 @Override 501 public void onReceive(Context context, Intent intent) { 502 String packageName = intent.getData().getSchemeSpecificPart(); 503 if (mPkgInfo == null || TextUtils.equals(mPkgInfo.packageName, packageName)) { 504 if (DEBUG) Log.d(TAG, "Package (" + packageName + ") removed. Removing" 505 + "NotificationSettingsBase."); 506 onPackageRemoved(); 507 } 508 } 509 }; 510 hasValidSound(NotificationChannel channel)511 boolean hasValidSound(NotificationChannel channel) { 512 return channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound()); 513 } 514 } 515