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