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