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.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.wifi.WifiContext;
28 import android.os.Handler;
29 import android.os.SystemClock;
30 import android.os.UserHandle;
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 WifiContext mContext;
56     private final WakeupNotificationFactory mWakeupNotificationFactory;
57     private final WifiNotificationManager 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.startActivityAsUser(new Intent(Settings.ACTION_WIFI_IP_SETTINGS)
81                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), UserHandle.CURRENT);
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( WifiContext context, WifiConfigManager wifiConfigManager, Handler handler, FrameworkFacade frameworkFacade, WakeupNotificationFactory wakeupNotificationFactory, WifiNotificationManager wifiNotificationManager)93     public WakeupOnboarding(
94             WifiContext context,
95             WifiConfigManager wifiConfigManager,
96             Handler handler,
97             FrameworkFacade frameworkFacade,
98             WakeupNotificationFactory wakeupNotificationFactory,
99             WifiNotificationManager wifiNotificationManager) {
100         mContext = context;
101         mWifiConfigManager = wifiConfigManager;
102         mHandler = handler;
103         mFrameworkFacade = frameworkFacade;
104         mWakeupNotificationFactory = wakeupNotificationFactory;
105         mNotificationManager = wifiNotificationManager;
106 
107         mIntentFilter = new IntentFilter();
108         mIntentFilter.addAction(ACTION_TURN_OFF_WIFI_WAKE);
109         mIntentFilter.addAction(ACTION_DISMISS_NOTIFICATION);
110         mIntentFilter.addAction(ACTION_OPEN_WIFI_PREFERENCES);
111     }
112 
113     /** Returns whether the user is onboarded. */
isOnboarded()114     public boolean isOnboarded() {
115         return mIsOnboarded;
116     }
117 
118     /** Shows the onboarding notification if applicable. */
maybeShowNotification()119     public void maybeShowNotification() {
120         maybeShowNotification(SystemClock.elapsedRealtime());
121     }
122 
123     @VisibleForTesting
maybeShowNotification(long timestamp)124     void maybeShowNotification(long timestamp) {
125         if (!shouldShowNotification(timestamp)) {
126             return;
127         }
128         Log.d(TAG, "Showing onboarding notification.");
129 
130         incrementTotalNotificationsShown();
131         mIsNotificationShowing = true;
132         mLastShownTimestamp = timestamp;
133 
134         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter,
135                 null /* broadcastPermission */, mHandler);
136         mNotificationManager.notify(WakeupNotificationFactory.ONBOARD_ID,
137                 mWakeupNotificationFactory.createOnboardingNotification());
138     }
139 
140     /**
141      * Increment the total number of shown notifications and onboard the user if reached the
142      * required amount.
143      */
incrementTotalNotificationsShown()144     private void incrementTotalNotificationsShown() {
145         mTotalNotificationsShown++;
146         if (mTotalNotificationsShown >= NOTIFICATIONS_UNTIL_ONBOARDED) {
147             setOnboarded();
148         } else {
149             mWifiConfigManager.saveToStore();
150         }
151     }
152 
shouldShowNotification(long timestamp)153     private boolean shouldShowNotification(long timestamp) {
154         if (isOnboarded() || mIsNotificationShowing) {
155             return false;
156         }
157 
158         return mLastShownTimestamp == NOT_SHOWN_TIMESTAMP
159                 || (timestamp - mLastShownTimestamp) > REQUIRED_NOTIFICATION_DELAY;
160     }
161 
162     /** Handles onboarding cleanup on stop. */
onStop()163     public void onStop() {
164         dismissNotification(false /* shouldOnboard */);
165     }
166 
dismissNotification(boolean shouldOnboard)167     private void dismissNotification(boolean shouldOnboard) {
168         if (!mIsNotificationShowing) {
169             return;
170         }
171 
172         if (shouldOnboard) {
173             setOnboarded();
174         }
175 
176         mContext.unregisterReceiver(mBroadcastReceiver);
177         mNotificationManager.cancel(WakeupNotificationFactory.ONBOARD_ID);
178         mIsNotificationShowing = false;
179     }
180 
181     /** Sets the user as onboarded and persists to store. */
setOnboarded()182     public void setOnboarded() {
183         if (mIsOnboarded) {
184             return;
185         }
186         Log.d(TAG, "Setting user as onboarded.");
187         mIsOnboarded = true;
188         mWifiConfigManager.saveToStore();
189     }
190 
191     /** Returns the {@link WakeupConfigStoreData.DataSource} for the onboarded status. */
getIsOnboadedDataSource()192     public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() {
193         return new IsOnboardedDataSource();
194     }
195 
196     /** Returns the {@link WakeupConfigStoreData.DataSource} for the notification status. */
getNotificationsDataSource()197     public WakeupConfigStoreData.DataSource<Integer> getNotificationsDataSource() {
198         return new NotificationsDataSource();
199     }
200 
201     private class IsOnboardedDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
202 
203         @Override
getData()204         public Boolean getData() {
205             return mIsOnboarded;
206         }
207 
208         @Override
setData(Boolean data)209         public void setData(Boolean data) {
210             mIsOnboarded = data;
211         }
212     }
213 
214     private class NotificationsDataSource implements WakeupConfigStoreData.DataSource<Integer> {
215 
216         @Override
getData()217         public Integer getData() {
218             return mTotalNotificationsShown;
219         }
220 
221         @Override
setData(Integer data)222         public void setData(Integer data) {
223             mTotalNotificationsShown = data;
224         }
225     }
226 }
227