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 package com.android.launcher3.settings;
17 
18 import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
21 
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.database.ContentObserver;
29 import android.os.Bundle;
30 import android.provider.Settings;
31 import android.util.AttributeSet;
32 import android.view.View;
33 
34 import androidx.fragment.app.DialogFragment;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceViewHolder;
37 
38 import com.android.launcher3.R;
39 import com.android.launcher3.notification.NotificationListener;
40 import com.android.launcher3.util.SettingsCache;
41 
42 /**
43  * A {@link Preference} for indicating notification dots status.
44  * Also has utility methods for updating UI based on dots status changes.
45  */
46 public class NotificationDotsPreference extends Preference
47         implements SettingsCache.OnChangeListener {
48 
49     private boolean mWidgetFrameVisible = false;
50 
51     /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
52     private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
53     private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
54 
55     private final ContentObserver mListenerListObserver =
56             new ContentObserver(MAIN_EXECUTOR.getHandler()) {
57         @Override
58         public void onChange(boolean selfChange) {
59             updateUI();
60         }
61     };
62 
NotificationDotsPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)63     public NotificationDotsPreference(
64             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
65         super(context, attrs, defStyleAttr, defStyleRes);
66     }
67 
NotificationDotsPreference(Context context, AttributeSet attrs, int defStyleAttr)68     public NotificationDotsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
69         super(context, attrs, defStyleAttr);
70     }
71 
NotificationDotsPreference(Context context, AttributeSet attrs)72     public NotificationDotsPreference(Context context, AttributeSet attrs) {
73         super(context, attrs);
74     }
75 
NotificationDotsPreference(Context context)76     public NotificationDotsPreference(Context context) {
77         super(context);
78     }
79 
80     @Override
onAttached()81     public void onAttached() {
82         super.onAttached();
83         SettingsCache.INSTANCE.get(getContext()).register(NOTIFICATION_BADGING_URI, this);
84         getContext().getContentResolver().registerContentObserver(
85                 Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS),
86                 false, mListenerListObserver);
87         updateUI();
88 
89         // Update intent
90         Bundle extras = new Bundle();
91         extras.putString(EXTRA_FRAGMENT_HIGHLIGHT_KEY, "notification_badging");
92 
93         setIntent(new Intent("android.settings.NOTIFICATION_SETTINGS")
94                 .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, extras));
95     }
96 
updateUI()97     private void updateUI() {
98         onSettingsChanged(SettingsCache.INSTANCE.get(getContext())
99                 .getValue(NOTIFICATION_BADGING_URI));
100     }
101 
102     @Override
onDetached()103     public void onDetached() {
104         super.onDetached();
105         SettingsCache.INSTANCE.get(getContext()).unregister(NOTIFICATION_BADGING_URI, this);
106         getContext().getContentResolver().unregisterContentObserver(mListenerListObserver);
107 
108     }
109 
setWidgetFrameVisible(boolean isVisible)110     private void setWidgetFrameVisible(boolean isVisible) {
111         if (mWidgetFrameVisible != isVisible) {
112             mWidgetFrameVisible = isVisible;
113             notifyChanged();
114         }
115     }
116 
117     @Override
onBindViewHolder(PreferenceViewHolder holder)118     public void onBindViewHolder(PreferenceViewHolder holder) {
119         super.onBindViewHolder(holder);
120 
121         View widgetFrame = holder.findViewById(android.R.id.widget_frame);
122         if (widgetFrame != null) {
123             widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
124         }
125     }
126 
127     @Override
onSettingsChanged(boolean enabled)128     public void onSettingsChanged(boolean enabled) {
129         int summary = enabled
130                 ? R.string.notification_dots_desc_on
131                 : R.string.notification_dots_desc_off;
132 
133         boolean serviceEnabled = true;
134         if (enabled) {
135             // Check if the listener is enabled or not.
136             String enabledListeners = Settings.Secure.getString(
137                     getContext().getContentResolver(), NOTIFICATION_ENABLED_LISTENERS);
138             ComponentName myListener =
139                     new ComponentName(getContext(), NotificationListener.class);
140             serviceEnabled = enabledListeners != null &&
141                     (enabledListeners.contains(myListener.flattenToString()) ||
142                             enabledListeners.contains(myListener.flattenToShortString()));
143             if (!serviceEnabled) {
144                 summary = R.string.title_missing_notification_access;
145             }
146         }
147         setWidgetFrameVisible(!serviceEnabled);
148         setFragment(serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
149         setSummary(summary);
150     }
151 
152     public static class NotificationAccessConfirmation
153             extends DialogFragment implements DialogInterface.OnClickListener {
154 
155         @Override
onCreateDialog(Bundle savedInstanceState)156         public Dialog onCreateDialog(Bundle savedInstanceState) {
157             final Context context = getActivity();
158             String msg = context.getString(R.string.msg_missing_notification_access,
159                     context.getString(R.string.derived_app_name));
160             return new AlertDialog.Builder(context)
161                     .setTitle(R.string.title_missing_notification_access)
162                     .setMessage(msg)
163                     .setNegativeButton(android.R.string.cancel, null)
164                     .setPositiveButton(R.string.title_change_settings, this)
165                     .create();
166         }
167 
168         @Override
onClick(DialogInterface dialogInterface, int i)169         public void onClick(DialogInterface dialogInterface, int i) {
170             ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
171             Bundle showFragmentArgs = new Bundle();
172             showFragmentArgs.putString(EXTRA_FRAGMENT_HIGHLIGHT_KEY, cn.flattenToString());
173 
174             Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
175                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
176                     .putExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY, cn.flattenToString())
177                     .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
178             getActivity().startActivity(intent);
179         }
180     }
181 }
182