Home | History | Annotate | Download | only in wifi
      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 
     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. */
    112     public boolean isOnboarded() {
    113         return mIsOnboarded;
    114     }
    115 
    116     /** Shows the onboarding notification if applicable. */
    117     public void maybeShowNotification() {
    118         maybeShowNotification(SystemClock.elapsedRealtime());
    119     }
    120 
    121     @VisibleForTesting
    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      */
    142     private void incrementTotalNotificationsShown() {
    143         mTotalNotificationsShown++;
    144         if (mTotalNotificationsShown >= NOTIFICATIONS_UNTIL_ONBOARDED) {
    145             setOnboarded();
    146         } else {
    147             mWifiConfigManager.saveToStore(false /* forceWrite */);
    148         }
    149     }
    150 
    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. */
    161     public void onStop() {
    162         dismissNotification(false /* shouldOnboard */);
    163     }
    164 
    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. */
    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     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. */
    198     public WakeupConfigStoreData.DataSource<Boolean> getIsOnboadedDataSource() {
    199         return new IsOnboardedDataSource();
    200     }
    201 
    202     /** Returns the {@link WakeupConfigStoreData.DataSource} for the notification status. */
    203     public WakeupConfigStoreData.DataSource<Integer> getNotificationsDataSource() {
    204         return new NotificationsDataSource();
    205     }
    206 
    207     private class IsOnboardedDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
    208 
    209         @Override
    210         public Boolean getData() {
    211             return mIsOnboarded;
    212         }
    213 
    214         @Override
    215         public void setData(Boolean data) {
    216             mIsOnboarded = data;
    217         }
    218     }
    219 
    220     private class NotificationsDataSource implements WakeupConfigStoreData.DataSource<Integer> {
    221 
    222         @Override
    223         public Integer getData() {
    224             return mTotalNotificationsShown;
    225         }
    226 
    227         @Override
    228         public void setData(Integer data) {
    229             mTotalNotificationsShown = data;
    230         }
    231     }
    232 }
    233