1/* 2 * Copyright (C) 2014 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 17package com.android.settings.notification; 18 19import android.app.Notification; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.ActivityInfo; 23import android.content.pm.ApplicationInfo; 24import android.content.pm.PackageInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.pm.ResolveInfo; 28import android.os.Bundle; 29import android.os.UserHandle; 30import android.preference.Preference; 31import android.preference.Preference.OnPreferenceChangeListener; 32import android.preference.Preference.OnPreferenceClickListener; 33import android.preference.SwitchPreference; 34import android.provider.Settings; 35import android.text.TextUtils; 36import android.util.ArrayMap; 37import android.util.Log; 38import android.widget.Toast; 39 40import com.android.internal.logging.MetricsLogger; 41import com.android.internal.widget.LockPatternUtils; 42import com.android.settings.AppHeader; 43import com.android.settings.R; 44import com.android.settings.SettingsPreferenceFragment; 45import com.android.settings.Utils; 46import com.android.settings.applications.AppInfoBase; 47import com.android.settings.applications.AppInfoWithHeader; 48import com.android.settings.notification.NotificationBackend.AppRow; 49 50import java.util.List; 51 52/** These settings are per app, so should not be returned in global search results. */ 53public class AppNotificationSettings extends SettingsPreferenceFragment { 54 private static final String TAG = "AppNotificationSettings"; 55 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 56 57 private static final String KEY_BLOCK = "block"; 58 private static final String KEY_PRIORITY = "priority"; 59 private static final String KEY_PEEKABLE = "peekable"; 60 private static final String KEY_SENSITIVE = "sensitive"; 61 private static final String KEY_APP_SETTINGS = "app_settings"; 62 63 private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT 64 = new Intent(Intent.ACTION_MAIN) 65 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES); 66 67 private final NotificationBackend mBackend = new NotificationBackend(); 68 69 private Context mContext; 70 private SwitchPreference mBlock; 71 private SwitchPreference mPriority; 72 private SwitchPreference mPeekable; 73 private SwitchPreference mSensitive; 74 private AppRow mAppRow; 75 private boolean mCreated; 76 private boolean mIsSystemPackage; 77 private int mUid; 78 79 @Override 80 public void onActivityCreated(Bundle savedInstanceState) { 81 super.onActivityCreated(savedInstanceState); 82 if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated); 83 if (mCreated) { 84 Log.w(TAG, "onActivityCreated: ignoring duplicate call"); 85 return; 86 } 87 mCreated = true; 88 if (mAppRow == null) return; 89 AppHeader.createAppHeader(this, mAppRow.icon, mAppRow.label, 90 AppInfoWithHeader.getInfoIntent(this, mAppRow.pkg)); 91 } 92 93 @Override 94 protected int getMetricsCategory() { 95 return MetricsLogger.NOTIFICATION_APP_NOTIFICATION; 96 } 97 98 @Override 99 public void onCreate(Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 mContext = getActivity(); 102 Intent intent = getActivity().getIntent(); 103 Bundle args = getArguments(); 104 if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent); 105 if (intent == null && args == null) { 106 Log.w(TAG, "No intent"); 107 toastAndFinish(); 108 return; 109 } 110 111 final String pkg = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_NAME) 112 ? args.getString(AppInfoBase.ARG_PACKAGE_NAME) 113 : intent.getStringExtra(Settings.EXTRA_APP_PACKAGE); 114 mUid = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_UID) 115 ? args.getInt(AppInfoBase.ARG_PACKAGE_UID) 116 : intent.getIntExtra(Settings.EXTRA_APP_UID, -1); 117 if (mUid == -1 || TextUtils.isEmpty(pkg)) { 118 Log.w(TAG, "Missing extras: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg + ", " 119 + Settings.EXTRA_APP_UID + " was " + mUid); 120 toastAndFinish(); 121 return; 122 } 123 124 if (DEBUG) Log.d(TAG, "Load details for pkg=" + pkg + " uid=" + mUid); 125 final PackageManager pm = getPackageManager(); 126 final PackageInfo info = findPackageInfo(pm, pkg, mUid); 127 if (info == null) { 128 Log.w(TAG, "Failed to find package info: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg 129 + ", " + Settings.EXTRA_APP_UID + " was " + mUid); 130 toastAndFinish(); 131 return; 132 } 133 mIsSystemPackage = Utils.isSystemPackage(pm, info); 134 135 addPreferencesFromResource(R.xml.app_notification_settings); 136 mBlock = (SwitchPreference) findPreference(KEY_BLOCK); 137 mPriority = (SwitchPreference) findPreference(KEY_PRIORITY); 138 mPeekable = (SwitchPreference) findPreference(KEY_PEEKABLE); 139 mSensitive = (SwitchPreference) findPreference(KEY_SENSITIVE); 140 141 mAppRow = mBackend.loadAppRow(pm, info.applicationInfo); 142 143 // load settings intent 144 ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>(); 145 rows.put(mAppRow.pkg, mAppRow); 146 collectConfigActivities(getPackageManager(), rows); 147 148 mBlock.setChecked(mAppRow.banned); 149 updateDependents(mAppRow.banned); 150 mPriority.setChecked(mAppRow.priority); 151 mPeekable.setChecked(mAppRow.peekable); 152 mSensitive.setChecked(mAppRow.sensitive); 153 154 mBlock.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 155 @Override 156 public boolean onPreferenceChange(Preference preference, Object newValue) { 157 final boolean banned = (Boolean) newValue; 158 if (banned) { 159 MetricsLogger.action(getActivity(), MetricsLogger.ACTION_BAN_APP_NOTES, pkg); 160 } 161 final boolean success = mBackend.setNotificationsBanned(pkg, mUid, banned); 162 if (success) { 163 updateDependents(banned); 164 } 165 return success; 166 } 167 }); 168 169 mPriority.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 170 @Override 171 public boolean onPreferenceChange(Preference preference, Object newValue) { 172 final boolean priority = (Boolean) newValue; 173 return mBackend.setHighPriority(pkg, mUid, priority); 174 } 175 }); 176 177 mPeekable.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 178 @Override 179 public boolean onPreferenceChange(Preference preference, Object newValue) { 180 final boolean peekable = (Boolean) newValue; 181 return mBackend.setPeekable(pkg, mUid, peekable); 182 } 183 }); 184 185 mSensitive.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 186 @Override 187 public boolean onPreferenceChange(Preference preference, Object newValue) { 188 final boolean sensitive = (Boolean) newValue; 189 return mBackend.setSensitive(pkg, mUid, sensitive); 190 } 191 }); 192 193 if (mAppRow.settingsIntent != null) { 194 findPreference(KEY_APP_SETTINGS).setOnPreferenceClickListener( 195 new OnPreferenceClickListener() { 196 @Override 197 public boolean onPreferenceClick(Preference preference) { 198 mContext.startActivity(mAppRow.settingsIntent); 199 return true; 200 } 201 }); 202 } else { 203 removePreference(KEY_APP_SETTINGS); 204 } 205 } 206 207 @Override 208 public void onResume() { 209 super.onResume(); 210 if (mUid != -1 && getPackageManager().getPackagesForUid(mUid) == null) { 211 // App isn't around anymore, must have been removed. 212 finish(); 213 } 214 } 215 216 private void updateDependents(boolean banned) { 217 final boolean lockscreenSecure = new LockPatternUtils(getActivity()).isSecure( 218 UserHandle.myUserId()); 219 final boolean lockscreenNotificationsEnabled = getLockscreenNotificationsEnabled(); 220 final boolean allowPrivate = getLockscreenAllowPrivateNotifications(); 221 222 setVisible(mBlock, !mIsSystemPackage); 223 setVisible(mPriority, mIsSystemPackage || !banned); 224 setVisible(mPeekable, mIsSystemPackage || !banned); 225 setVisible(mSensitive, mIsSystemPackage || !banned && lockscreenSecure 226 && lockscreenNotificationsEnabled && allowPrivate); 227 } 228 229 private void setVisible(Preference p, boolean visible) { 230 final boolean isVisible = getPreferenceScreen().findPreference(p.getKey()) != null; 231 if (isVisible == visible) return; 232 if (visible) { 233 getPreferenceScreen().addPreference(p); 234 } else { 235 getPreferenceScreen().removePreference(p); 236 } 237 } 238 239 private boolean getLockscreenNotificationsEnabled() { 240 return Settings.Secure.getInt(getContentResolver(), 241 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; 242 } 243 244 private boolean getLockscreenAllowPrivateNotifications() { 245 return Settings.Secure.getInt(getContentResolver(), 246 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; 247 } 248 249 private void toastAndFinish() { 250 Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show(); 251 getActivity().finish(); 252 } 253 254 private static PackageInfo findPackageInfo(PackageManager pm, String pkg, int uid) { 255 final String[] packages = pm.getPackagesForUid(uid); 256 if (packages != null && pkg != null) { 257 final int N = packages.length; 258 for (int i = 0; i < N; i++) { 259 final String p = packages[i]; 260 if (pkg.equals(p)) { 261 try { 262 return pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); 263 } catch (NameNotFoundException e) { 264 Log.w(TAG, "Failed to load package " + pkg, e); 265 } 266 } 267 } 268 } 269 return null; 270 } 271 272 public static List<ResolveInfo> queryNotificationConfigActivities(PackageManager pm) { 273 if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is " 274 + APP_NOTIFICATION_PREFS_CATEGORY_INTENT); 275 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( 276 APP_NOTIFICATION_PREFS_CATEGORY_INTENT, 277 0 //PackageManager.MATCH_DEFAULT_ONLY 278 ); 279 return resolveInfos; 280 } 281 282 public static void collectConfigActivities(PackageManager pm, ArrayMap<String, AppRow> rows) { 283 final List<ResolveInfo> resolveInfos = queryNotificationConfigActivities(pm); 284 applyConfigActivities(pm, rows, resolveInfos); 285 } 286 287 public static void applyConfigActivities(PackageManager pm, ArrayMap<String, AppRow> rows, 288 List<ResolveInfo> resolveInfos) { 289 if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" 290 + (resolveInfos.size() == 0 ? " ;_;" : "")); 291 for (ResolveInfo ri : resolveInfos) { 292 final ActivityInfo activityInfo = ri.activityInfo; 293 final ApplicationInfo appInfo = activityInfo.applicationInfo; 294 final AppRow row = rows.get(appInfo.packageName); 295 if (row == null) { 296 if (DEBUG) Log.v(TAG, "Ignoring notification preference activity (" 297 + activityInfo.name + ") for unknown package " 298 + activityInfo.packageName); 299 continue; 300 } 301 if (row.settingsIntent != null) { 302 if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity (" 303 + activityInfo.name + ") for package " 304 + activityInfo.packageName); 305 continue; 306 } 307 row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT) 308 .setClassName(activityInfo.packageName, activityInfo.name); 309 } 310 } 311} 312