1 /*
2  * Copyright (C) 2021 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.car.settings.wifi;
18 
19 import android.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.net.TetheringManager;
25 import android.net.wifi.SoftApInfo;
26 import android.net.wifi.WifiClient;
27 import android.net.wifi.WifiManager;
28 
29 import androidx.annotation.VisibleForTesting;
30 import androidx.lifecycle.Lifecycle;
31 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
32 
33 import java.util.List;
34 
35 /**
36  * Consolidates Wifi tethering logic into one handler so we can have consistent logic across various
37  * parts of the Settings app.
38  */
39 public class WifiTetheringHandler {
40 
41     private final Context mContext;
42     private final CarWifiManager mCarWifiManager;
43     private final TetheringManager mTetheringManager;
44     private final WifiTetheringAvailabilityListener mWifiTetheringAvailabilityListener;
45     private boolean mRestartBooked = false;
46     private boolean mMonitorRestarts;
47 
48     private final WifiManager.SoftApCallback mSoftApCallback = new WifiManager.SoftApCallback() {
49         @Override
50         public void onStateChanged(int state, int failureReason) {
51             handleWifiApStateChanged(state);
52         }
53 
54         @Override
55         public void onConnectedClientsChanged(@NonNull SoftApInfo info,
56                 @NonNull List<WifiClient> clients) {
57             mWifiTetheringAvailabilityListener.onConnectedClientsChanged(clients.size());
58         }
59     };
60 
61     private final BroadcastReceiver mRestartReceiver = new BroadcastReceiver() {
62         @Override
63         public void onReceive(Context context, Intent intent) {
64             if (mCarWifiManager != null && mCarWifiManager.isWifiApEnabled()) {
65                 restartTethering();
66             }
67         }
68     };
69 
WifiTetheringHandler(Context context, Lifecycle lifecycle, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener)70     public WifiTetheringHandler(Context context, Lifecycle lifecycle,
71             WifiTetheringAvailabilityListener wifiTetherAvailabilityListener) {
72         this(context, lifecycle, wifiTetherAvailabilityListener, /* monitorRestarts= */ true);
73     }
74 
WifiTetheringHandler(Context context, Lifecycle lifecycle, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener, boolean monitorRestarts)75     public WifiTetheringHandler(Context context, Lifecycle lifecycle,
76             WifiTetheringAvailabilityListener wifiTetherAvailabilityListener,
77             boolean monitorRestarts) {
78         this(context, new CarWifiManager(context, lifecycle),
79                 context.getSystemService(TetheringManager.class), wifiTetherAvailabilityListener,
80                 /* monitorRestarts= */ monitorRestarts);
81     }
82 
WifiTetheringHandler(Context context, CarWifiManager carWifiManager, TetheringManager tetheringManager, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener, boolean monitorRestarts)83     public WifiTetheringHandler(Context context, CarWifiManager carWifiManager,
84             TetheringManager tetheringManager, WifiTetheringAvailabilityListener
85             wifiTetherAvailabilityListener, boolean monitorRestarts) {
86         mContext = context;
87         mCarWifiManager = carWifiManager;
88         mTetheringManager = tetheringManager;
89         mWifiTetheringAvailabilityListener = wifiTetherAvailabilityListener;
90         mMonitorRestarts = monitorRestarts;
91     }
92 
93     /**
94      * Handles operations that should happen in host's onStartInternal().
95      */
onStartInternal()96     public void onStartInternal() {
97         mCarWifiManager.registerSoftApCallback(mContext.getMainExecutor(), mSoftApCallback);
98         if (mMonitorRestarts) {
99             LocalBroadcastManager.getInstance(mContext).registerReceiver(mRestartReceiver,
100                     new IntentFilter(
101                             WifiTetherBasePreferenceController.ACTION_RESTART_WIFI_TETHERING));
102         }
103     }
104 
105     /**
106      * Handles operations that should happen in host's onStopInternal().
107      */
onStopInternal()108     public void onStopInternal() {
109         mCarWifiManager.unregisterSoftApCallback(mSoftApCallback);
110         if (mMonitorRestarts) {
111             LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mRestartReceiver);
112         }
113     }
114 
115     /**
116      * Returns whether wifi tethering is enabled
117      * @return whether wifi tethering is enabled
118      */
isWifiTetheringEnabled()119     public boolean isWifiTetheringEnabled() {
120         return mCarWifiManager.isWifiApEnabled();
121     }
122 
123     /**
124      * Changes the Wifi tethering state
125      *
126      * @param enable Whether to attempt to turn Wifi tethering on or off
127      */
updateWifiTetheringState(boolean enable)128     public void updateWifiTetheringState(boolean enable) {
129         if (enable) {
130             startTethering();
131         } else {
132             stopTethering();
133         }
134     }
135 
136     @VisibleForTesting
handleWifiApStateChanged(int state)137     void handleWifiApStateChanged(int state) {
138         switch (state) {
139             case WifiManager.WIFI_AP_STATE_ENABLING:
140                 mWifiTetheringAvailabilityListener.disablePreference();
141                 break;
142             case WifiManager.WIFI_AP_STATE_ENABLED:
143                 mWifiTetheringAvailabilityListener.enablePreference();
144                 mWifiTetheringAvailabilityListener.onWifiTetheringAvailable();
145                 break;
146             case WifiManager.WIFI_AP_STATE_DISABLING:
147                 mWifiTetheringAvailabilityListener.disablePreference();
148                 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable();
149                 break;
150             case WifiManager.WIFI_AP_STATE_DISABLED:
151                 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable();
152                 mWifiTetheringAvailabilityListener.enablePreference();
153                 if (mRestartBooked) {
154                     // Hotspot was disabled as part of a restart request - we can now re-enable it
155                     mWifiTetheringAvailabilityListener.disablePreference();
156                     startTethering();
157                     mRestartBooked = false;
158                 }
159                 break;
160             default:
161                 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable();
162                 mWifiTetheringAvailabilityListener.enablePreference();
163                 break;
164         }
165     }
166 
startTethering()167     private void startTethering() {
168         WifiTetherUtil.startTethering(mTetheringManager,
169                 new TetheringManager.StartTetheringCallback() {
170                     @Override
171                     public void onTetheringFailed(int error) {
172                         mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable();
173                         mWifiTetheringAvailabilityListener.enablePreference();
174                     }
175                 });
176     }
177 
stopTethering()178     private void stopTethering() {
179         WifiTetherUtil.stopTethering(mTetheringManager);
180     }
181 
restartTethering()182     private void restartTethering() {
183         stopTethering();
184         mRestartBooked = true;
185     }
186 
187     /**
188      * Interface for receiving Wifi tethering status updates
189      */
190     public interface WifiTetheringAvailabilityListener {
191         /**
192          * Callback for when Wifi tethering is available
193          */
onWifiTetheringAvailable()194         void onWifiTetheringAvailable();
195 
196         /**
197          * Callback for when Wifi tethering is unavailable
198          */
onWifiTetheringUnavailable()199         void onWifiTetheringUnavailable();
200 
201         /**
202          * Callback for when the number of tethered devices has changed
203          * @param clientCount number of connected clients
204          */
onConnectedClientsChanged(int clientCount)205         default void onConnectedClientsChanged(int clientCount){
206         }
207 
208         /**
209          * Listener should allow further changes to Wifi tethering
210          */
enablePreference()211         void enablePreference();
212 
213         /**
214          * Listener should disallow further changes to Wifi tethering
215          */
disablePreference()216         void disablePreference();
217     }
218 }
219