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