1 /*
2  * Copyright 2018 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.server.wifi;
18 
19 import static com.android.server.wifi.WakeupNotificationFactory.ACTION_DISMISS_NOTIFICATION;
20 import static com.android.server.wifi.WakeupNotificationFactory.ACTION_OPEN_WIFI_PREFERENCES;
21 import static com.android.server.wifi.WakeupNotificationFactory.ACTION_TURN_OFF_WIFI_WAKE;
22 
23 import android.app.NotificationManager;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.SystemClock;
31 import android.provider.Settings;
32 import android.text.format.DateUtils;
33 import android.util.Log;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 /**
38  * Manages the WiFi Wake onboarding notification.
39  *
40  * <p>If a user disables wifi with Wifi Wake enabled, this notification is shown to explain that
41  * wifi may turn back on automatically. It will be displayed up to 3 times, or until the
42  * user either interacts with the onboarding notification in some way (e.g. dismiss, tap) or
43  * manually enables/disables the feature in WifiSettings.
44  */
45 public class WakeupOnboarding {
46 
47     private static final String TAG = "WakeupOnboarding";
48 
49     @VisibleForTesting
50     static final int NOTIFICATIONS_UNTIL_ONBOARDED = 3;
51     @VisibleForTesting
52     static final long REQUIRED_NOTIFICATION_DELAY = DateUtils.DAY_IN_MILLIS;
53     private static final long NOT_SHOWN_TIMESTAMP = -1;
54 
55     private final Context mContext;
56     private final WakeupNotificationFactory mWakeupNotificationFactory;
57     private NotificationManager mNotificationManager;
58     private final Handler mHandler;
59     private final WifiConfigManager mWifiConfigManager;
60     private final IntentFilter mIntentFilter;
61     private final FrameworkFacade mFrameworkFacade;
62 
63     private boolean mIsOnboarded;
64     private int mTotalNotificationsShown;
65     private long mLastShownTimestamp = NOT_SHOWN_TIMESTAMP;
66     private boolean mIsNotificationShowing;
67 
68     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
69         @Override
70         public void onReceive(Context context, Intent intent) {
71             switch (intent.getAction()) {
72                 case ACTION_TURN_OFF_WIFI_WAKE:
73                     mFrameworkFacade.setIntegerSetting(mContext,
74                             Settings.Global.WIFI_WAKEUP_ENABLED, 0);
75                     dismissNotification(true /* shouldOnboard */);
76                     break;
77                 case ACTION_OPEN_WIFI_PREFERENCES:
78                     // Close notification drawer before opening preferences.
79                     mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
80                     mContext.startActivity(new Intent(Settings.ACTION_WIFI_IP_SETTINGS)
81                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
82                     dismissNotification(true /* shouldOnboard */);
83                     break;
84                 case ACTION_DISMISS_NOTIFICATION:
85                     dismissNotification(true /* shouldOnboard */);
86                     break;
87                 default:
88                     Log.e(TAG, "Unknown action " + intent.getAction());
89             }
90         }
91     };
92 
WakeupOnboarding( Context context, WifiConfigManager wifiConfigManager, Looper looper, FrameworkFacade frameworkFacade, WakeupNotificationFactory wakeupNotificationFactory)93     public WakeupOnboarding(
94             Context context,
95             WifiConfigManager wifiConfigManager,
96             Looper looper,
97             FrameworkFacade frameworkFacade,
98             WakeupNotificationFactory wakeupNotificationFactory) {
99         mContext = context;
100         mWifiConfigManager = wifiConfigManager;
101         mHandler = new Handler(looper);
102         mFrameworkFacade = frameworkFacade;
103         mWakeupNotificationFactory = wakeupNotificationFactory;
104 
105         mIntentFilter = new IntentFilter();
106         mIntentFilter.addAction(ACTION_TURN_OFF_WIFI_WAKE);
107         mIntentFilter.addAction(ACTION_DISMISS_NOTIFICATION);
108         mIntentFilter.addAction(ACTION_OPEN_WIFI_PREFERENCES);
109     }
110 
111     /** Returns whether the user is onboarded. */
isOnboarded()112     public boolean isOnboarded() {
113         return mIsOnboarded;
114     }
115 
116     /** Shows the onboarding notification if applicable. */
maybeShowNotification()117     public void maybeShowNotification() {
118         maybeShowNotification(SystemClock.elapsedRealtime());
119     }
120 
121     @VisibleForTesting
maybeShowNotification(long timestamp)122     void maybeShowNotification(long timestamp) {
123         if (!shouldShowNotification(timestamp)) {
124             return;
125         }
126         Log.d(TAG, "Showing onboarding notification.");
127 
128         incrementTotalNotificationsShown();
129         mIsNotificationShowing = true;
130         mLastShownTimestamp = timestamp;
131 
132         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter,
133                 null /* broadcastPermission */, mHandler);
134         getNotificationManager().notify(WakeupNotificationFactory.ONBOARD_ID,
135                 mWakeupNotificationFactory.createOnboardingNotification());
136     }
137 
138     /**
139      * Increment the total number of shown notifications and onboard the user if reached the
140      * required amount.
141      */
incrementTotalNotificationsShown()142     private void incrementTotalNotificationsShown() {
143         mTotalNotificationsShown++;
144         if (mTotalNotificationsShown >= NOTIFICATIONS_UNTIL_ONBOARDED) {
145             setOnboarded();
146         } else {
147             mWifiConfigManager.saveToStore(false /* forceWrite */);
148         }
149     }
150 
shouldShowNotification(long timestamp)151     private boolean shouldShowNotification(long timestamp) {
152         if (isOnboarded() || mIsNotificationShowing) {
153             return false;
154         }
155 
156         return mLastShownTimestamp == NOT_SHOWN_TIMESTAMP
157                 || (timestamp - mLastShownTimestamp) > REQUIRED_NOTIFICATION_DELAY;
158     }
159 
160     /** Handles onboarding cleanup on stop. */
onStop()161     public void onStop() {
162         dismissNotification(false /* shouldOnboard */);
163     }
164 
dismissNotification(boolean shouldOnboard)165     private void dismissNotification(boolean shouldOnboard) {
166         if (!mIsNotificationShowing) {
167             return;
168         }
169 
170         if (shouldOnboard) {
171             setOnboarded();
172         }
173 
174         mContext.unregisterReceiver(mBroadcastReceiver);
175         getNotificationManager().cancel(WakeupNotificationFactory.ONBOARD_ID);
176         mIsNotificationShowing = false;
177     }
178 
179     /** Sets the user as onboarded and persists to store. */
setOnboarded()180     public void setOnboarded() {
181         if (mIsOnboarded) {
182             return;
183         }
184         Log.d(TAG, "Setting user as onboarded.");
185         mIsOnboarded = true;
186         mWifiConfigManager.saveToStore(false /* forceWrite */);
187     }
188 
getNotificationManager()189     private NotificationManager getNotificationManager() {
190         if (mNotificationManager == null) {
191             mNotificationManager = (NotificationManager)
192                     mContext.getSystemService(Context.NOTIFICATION_SERVICE);
193         }
194         return mNotificationManager;
195     }
196 
197     /** Returns the {@link WakeupConfigStoreData.DataSource} for the onboarded status. */
getIsOnboadedDataSource()198     public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() {
199         return new IsOnboardedDataSource();
200     }
201 
202     /** Returns the {@link WakeupConfigStoreData.DataSource} for the notification status. */
getNotificationsDataSource()203     public WakeupConfigStoreData.DataSource<Integer> getNotificationsDataSource() {
204         return new NotificationsDataSource();
205     }
206 
207     private class IsOnboardedDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
208 
209         @Override
getData()210         public Boolean getData() {
211             return mIsOnboarded;
212         }
213 
214         @Override
setData(Boolean data)215         public void setData(Boolean data) {
216             mIsOnboarded = data;
217         }
218     }
219 
220     private class NotificationsDataSource implements WakeupConfigStoreData.DataSource<Integer> {
221 
222         @Override
getData()223         public Integer getData() {
224             return mTotalNotificationsShown;
225         }
226 
227         @Override
setData(Integer data)228         public void setData(Integer data) {
229             mTotalNotificationsShown = data;
230         }
231     }
232 }
233