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