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