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.os.Handler; 28 import android.os.SystemClock; 29 import android.provider.Settings; 30 import android.text.format.DateUtils; 31 import android.util.Log; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 35 /** 36 * Manages the WiFi Wake onboarding notification. 37 * 38 * <p>If a user disables wifi with Wifi Wake enabled, this notification is shown to explain that 39 * wifi may turn back on automatically. It will be displayed up to 3 times, or until the 40 * user either interacts with the onboarding notification in some way (e.g. dismiss, tap) or 41 * manually enables/disables the feature in WifiSettings. 42 */ 43 public class WakeupOnboarding { 44 45 private static final String TAG = "WakeupOnboarding"; 46 47 @VisibleForTesting 48 static final int NOTIFICATIONS_UNTIL_ONBOARDED = 3; 49 @VisibleForTesting 50 static final long REQUIRED_NOTIFICATION_DELAY = DateUtils.DAY_IN_MILLIS; 51 private static final long NOT_SHOWN_TIMESTAMP = -1; 52 53 private final WifiContext mContext; 54 private final WakeupNotificationFactory mWakeupNotificationFactory; 55 private final WifiNotificationManager mNotificationManager; 56 private final Handler mHandler; 57 private final WifiConfigManager mWifiConfigManager; 58 private final IntentFilter mIntentFilter; 59 private final FrameworkFacade mFrameworkFacade; 60 61 private boolean mIsOnboarded; 62 private int mTotalNotificationsShown; 63 private long mLastShownTimestamp = NOT_SHOWN_TIMESTAMP; 64 private boolean mIsNotificationShowing; 65 66 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 67 @Override 68 public void onReceive(Context context, Intent intent) { 69 switch (intent.getAction()) { 70 case ACTION_TURN_OFF_WIFI_WAKE: 71 mFrameworkFacade.setIntegerSetting(mContext, 72 Settings.Global.WIFI_WAKEUP_ENABLED, 0); 73 dismissNotification(true /* shouldOnboard */); 74 break; 75 case ACTION_OPEN_WIFI_PREFERENCES: 76 // Close notification drawer before opening preferences. 77 mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 78 mContext.startActivity(new Intent(Settings.ACTION_WIFI_IP_SETTINGS) 79 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 80 dismissNotification(true /* shouldOnboard */); 81 break; 82 case ACTION_DISMISS_NOTIFICATION: 83 dismissNotification(true /* shouldOnboard */); 84 break; 85 default: 86 Log.e(TAG, "Unknown action " + intent.getAction()); 87 } 88 } 89 }; 90 WakeupOnboarding( WifiContext context, WifiConfigManager wifiConfigManager, Handler handler, FrameworkFacade frameworkFacade, WakeupNotificationFactory wakeupNotificationFactory, WifiNotificationManager wifiNotificationManager)91 public WakeupOnboarding( 92 WifiContext context, 93 WifiConfigManager wifiConfigManager, 94 Handler handler, 95 FrameworkFacade frameworkFacade, 96 WakeupNotificationFactory wakeupNotificationFactory, 97 WifiNotificationManager wifiNotificationManager) { 98 mContext = context; 99 mWifiConfigManager = wifiConfigManager; 100 mHandler = handler; 101 mFrameworkFacade = frameworkFacade; 102 mWakeupNotificationFactory = wakeupNotificationFactory; 103 mNotificationManager = wifiNotificationManager; 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 mNotificationManager.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 mNotificationManager.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 189 /** Returns the {@link WakeupConfigStoreData.DataSource} for the onboarded status. */ getIsOnboadedDataSource()190 public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() { 191 return new IsOnboardedDataSource(); 192 } 193 194 /** Returns the {@link WakeupConfigStoreData.DataSource} for the notification status. */ getNotificationsDataSource()195 public WakeupConfigStoreData.DataSource<Integer> getNotificationsDataSource() { 196 return new NotificationsDataSource(); 197 } 198 199 private class IsOnboardedDataSource implements WakeupConfigStoreData.DataSource<Boolean> { 200 201 @Override getData()202 public Boolean getData() { 203 return mIsOnboarded; 204 } 205 206 @Override setData(Boolean data)207 public void setData(Boolean data) { 208 mIsOnboarded = data; 209 } 210 } 211 212 private class NotificationsDataSource implements WakeupConfigStoreData.DataSource<Integer> { 213 214 @Override getData()215 public Integer getData() { 216 return mTotalNotificationsShown; 217 } 218 219 @Override setData(Integer data)220 public void setData(Integer data) { 221 mTotalNotificationsShown = data; 222 } 223 } 224 } 225