1 /* 2 * Copyright (C) 2017 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 package com.android.networkrecommendation.wakeup; 17 18 import static com.android.networkrecommendation.Constants.TAG; 19 import static com.android.networkrecommendation.util.NotificationChannelUtil.CHANNEL_ID_WAKEUP; 20 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.res.Resources; 29 import android.net.wifi.WifiConfiguration; 30 import android.net.wifi.WifiInfo; 31 import android.net.wifi.WifiManager; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.provider.Settings; 35 import android.support.annotation.NonNull; 36 import android.support.annotation.VisibleForTesting; 37 import android.text.TextUtils; 38 import android.util.ArraySet; 39 import com.android.networkrecommendation.R; 40 import com.android.networkrecommendation.config.G; 41 import com.android.networkrecommendation.config.Preferences; 42 import com.android.networkrecommendation.scoring.util.HashUtil; 43 import com.android.networkrecommendation.util.Blog; 44 import com.android.networkrecommendation.util.NotificationChannelUtil; 45 import java.util.Set; 46 import java.util.concurrent.TimeUnit; 47 48 /** 49 * Helper class for logging Wi-Fi Wakeup sessions and showing showing notifications for {@link 50 * WifiWakeupController}. 51 */ 52 public class WifiWakeupHelper { 53 /** Unique ID used for the Wi-Fi Enabled notification. */ 54 private static final int NOTIFICATION_ID = R.string.wifi_wakeup_enabled_notification_title; 55 56 @VisibleForTesting 57 static final String ACTION_WIFI_SETTINGS = 58 "com.android.networkrecommendation.wakeup.ACTION_WIFI_SETTINGS"; 59 60 @VisibleForTesting 61 static final String ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION = 62 "com.android.networkrecommendation.wakeup.ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION"; 63 64 private static final long NETWORK_CONNECTED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30); 65 private static final IntentFilter INTENT_FILTER = new IntentFilter(); 66 67 static { 68 INTENT_FILTER.addAction(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION); 69 INTENT_FILTER.addAction(ACTION_WIFI_SETTINGS); 70 INTENT_FILTER.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 71 } 72 73 private final Context mContext; 74 private final Resources mResources; 75 private final NotificationManager mNotificationManager; 76 private final Handler mHandler; 77 private final WifiManager mWifiManager; 78 79 /** Whether the wakeup notification is currently displayed. */ 80 private boolean mNotificationShown; 81 /** True when the device is still connected to the first connected ssid since wakeup. */ 82 private boolean mWifiSessionStarted; 83 /** The first connected ssid after wakeup enabled wifi. */ 84 private String mConnectedSsid; 85 86 private final BroadcastReceiver mBroadcastReceiver = 87 new BroadcastReceiver() { 88 @Override 89 public void onReceive(Context context, Intent intent) { 90 try { 91 if (ACTION_WIFI_SETTINGS.equals(intent.getAction())) { 92 mContext.startActivity( 93 new Intent(Settings.ACTION_WIFI_SETTINGS) 94 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 95 } else if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals( 96 intent.getAction())) { 97 cancelNotificationIfNeeded(); 98 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals( 99 intent.getAction())) { 100 networkStateChanged(); 101 } 102 } catch (RuntimeException re) { 103 // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident 104 // this is not going to throw. 105 Blog.e(TAG, re, "RuntimeException in broadcast receiver."); 106 } 107 } 108 }; 109 WifiWakeupHelper( Context context, Resources resources, Handler handler, NotificationManager notificationManager, WifiManager wifiManager)110 public WifiWakeupHelper( 111 Context context, 112 Resources resources, 113 Handler handler, 114 NotificationManager notificationManager, 115 WifiManager wifiManager) { 116 mContext = context; 117 mResources = resources; 118 mNotificationManager = notificationManager; 119 mHandler = handler; 120 mWifiManager = wifiManager; 121 mWifiSessionStarted = false; 122 mNotificationShown = false; 123 mConnectedSsid = null; 124 } 125 126 /** 127 * Start tracking a wifi wakeup session. Optionally show a notification that Wi-Fi has been 128 * enabled by Wi-Fi Wakeup if one has not been displayed for this {@link WifiConfiguration}. 129 * 130 * @param wifiConfiguration the {@link WifiConfiguration} that triggered Wi-Fi to wakeup 131 */ startWifiSession(@onNull WifiConfiguration wifiConfiguration)132 public void startWifiSession(@NonNull WifiConfiguration wifiConfiguration) { 133 mContext.registerReceiver( 134 mBroadcastReceiver, INTENT_FILTER, null /* broadcastPermission*/, mHandler); 135 mWifiSessionStarted = true; 136 mHandler.postDelayed( 137 () -> { 138 if (mWifiSessionStarted && mConnectedSsid == null) { 139 endWifiSession(); 140 } 141 }, 142 NETWORK_CONNECTED_TIMEOUT_MILLIS); 143 144 Set<String> hashedSsidSet = Preferences.ssidsForWakeupShown.get(); 145 String hashedSsid = HashUtil.getSsidHash(wifiConfiguration.SSID); 146 if (hashedSsidSet.isEmpty()) { 147 hashedSsidSet = new ArraySet<>(); 148 } else if (hashedSsidSet.contains(hashedSsid)) { 149 Blog.i( 150 TAG, 151 "Already showed Wi-Fi Enabled notification for ssid: %s", 152 Blog.pii(wifiConfiguration.SSID, G.Netrec.enableSensitiveLogging.get())); 153 return; 154 } 155 hashedSsidSet.add(hashedSsid); 156 Preferences.ssidsForWakeupShown.put(hashedSsidSet); 157 158 String title = mResources.getString(R.string.wifi_wakeup_enabled_notification_title); 159 String summary = 160 mResources.getString( 161 R.string.wifi_wakeup_enabled_notification_context, wifiConfiguration.SSID); 162 PendingIntent savedNetworkSettingsPendingIntent = 163 PendingIntent.getBroadcast( 164 mContext, 165 0, 166 new Intent(ACTION_WIFI_SETTINGS), 167 PendingIntent.FLAG_UPDATE_CURRENT); 168 PendingIntent deletePendingIntent = 169 PendingIntent.getBroadcast( 170 mContext, 171 0, 172 new Intent(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION), 173 PendingIntent.FLAG_UPDATE_CURRENT); 174 Bundle extras = new Bundle(); 175 extras.putString( 176 Notification.EXTRA_SUBSTITUTE_APP_NAME, 177 mResources.getString(R.string.notification_channel_group_name)); 178 int smallIcon = R.drawable.ic_signal_wifi_statusbar_not_connected; 179 Notification.Builder notificationBuilder = 180 new Notification.Builder(mContext) 181 .setContentTitle(title) 182 .setSmallIcon(smallIcon) 183 .setColor(mContext.getColor(R.color.color_tint)) 184 .setStyle(new Notification.BigTextStyle().bigText(summary)) 185 .setAutoCancel(true) 186 .setShowWhen(false) 187 .setDeleteIntent(deletePendingIntent) 188 .setPriority(Notification.PRIORITY_LOW) 189 .setVisibility(Notification.VISIBILITY_PUBLIC) 190 .setCategory(Notification.CATEGORY_STATUS) 191 .setContentIntent(savedNetworkSettingsPendingIntent) 192 .setLocalOnly(true) 193 .addExtras(extras); 194 NotificationChannelUtil.setChannel(notificationBuilder, CHANNEL_ID_WAKEUP); 195 mNotificationManager.notify(TAG, NOTIFICATION_ID, notificationBuilder.build()); 196 mNotificationShown = true; 197 } 198 networkStateChanged()199 private void networkStateChanged() { 200 if (!mWifiManager.isWifiEnabled()) { 201 endWifiSession(); 202 return; 203 } 204 205 WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 206 String ssid = wifiInfo == null ? null : wifiInfo.getSSID(); 207 if (mConnectedSsid == null) { 208 if (!TextUtils.isEmpty(ssid)) { 209 mConnectedSsid = ssid; 210 } 211 } else if (!TextUtils.equals(ssid, mConnectedSsid)) { 212 endWifiSession(); 213 } 214 } 215 endWifiSession()216 private void endWifiSession() { 217 if (mWifiSessionStarted) { 218 mWifiSessionStarted = false; 219 cancelNotificationIfNeeded(); 220 mConnectedSsid = null; 221 mContext.unregisterReceiver(mBroadcastReceiver); 222 } 223 } 224 cancelNotificationIfNeeded()225 private void cancelNotificationIfNeeded() { 226 if (mNotificationShown) { 227 mNotificationShown = false; 228 mNotificationManager.cancel(TAG, NOTIFICATION_ID); 229 } 230 } 231 } 232