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