1 /*
2  * Copyright (C) 2024 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.wifi;
18 
19 import static android.car.settings.CarSettings.Global.ENABLE_PERSISTENT_TETHERING;
20 import static android.net.TetheringManager.TETHERING_WIFI;
21 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
22 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
23 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
24 
25 import static com.android.car.CarServiceUtils.getHandlerThread;
26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
27 
28 import android.car.Car;
29 import android.car.builtin.util.Slogf;
30 import android.car.feature.FeatureFlags;
31 import android.car.feature.FeatureFlagsImpl;
32 import android.car.hardware.power.CarPowerManager;
33 import android.car.hardware.power.ICarPowerStateListener;
34 import android.car.wifi.ICarWifi;
35 import android.content.Context;
36 import android.content.SharedPreferences;
37 import android.database.ContentObserver;
38 import android.net.TetheringManager;
39 import android.net.TetheringManager.StartTetheringCallback;
40 import android.net.wifi.SoftApConfiguration;
41 import android.net.wifi.WifiManager;
42 import android.net.wifi.WifiManager.SoftApCallback;
43 import android.os.Handler;
44 import android.os.HandlerThread;
45 import android.provider.Settings;
46 import android.text.TextUtils;
47 import android.util.proto.ProtoOutputStream;
48 
49 import com.android.car.CarLocalServices;
50 import com.android.car.CarLog;
51 import com.android.car.CarServiceBase;
52 import com.android.car.CarServiceUtils;
53 import com.android.car.R;
54 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
55 import com.android.car.internal.util.IndentingPrintWriter;
56 import com.android.car.power.CarPowerManagementService;
57 import com.android.car.user.CarUserService;
58 import com.android.internal.annotations.GuardedBy;
59 
60 /**
61  * CarWifiService manages Wi-Fi functionality.
62  */
63 public final class CarWifiService extends ICarWifi.Stub implements CarServiceBase {
64     private static final String TAG = CarLog.tagFor(CarWifiService.class);
65     private static final String SHARED_PREF_NAME = "com.android.car.wifi.car_wifi_service";
66     private static final String KEY_PERSIST_TETHERING_ENABLED_LAST =
67             "persist_tethering_enabled_last";
68 
69     private final Object mLock = new Object();
70     private final Context mContext;
71     private final boolean mIsPersistTetheringCapabilitiesEnabled;
72     private final boolean mIsPersistTetheringSettingEnabled;
73     private final WifiManager mWifiManager;
74     private final TetheringManager mTetheringManager;
75     private final CarPowerManagementService mCarPowerManagementService;
76     private final CarUserService mCarUserService;
77     private final HandlerThread mHandlerThread =
78             getHandlerThread(getClass().getSimpleName());
79     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
80     private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
81 
82     private final ICarPowerStateListener mCarPowerStateListener =
83             new ICarPowerStateListener.Stub() {
84                 @Override
85                 public void onStateChanged(int state, long expirationTimeMs) {
86                     if (state == CarPowerManager.STATE_ON) {
87                         onStateOn();
88                     }
89                 }
90             };
91 
92     private final SoftApCallback mSoftApCallback =
93             new SoftApCallback() {
94                 @Override
95                 public void onStateChanged(int state, int failureReason) {
96                     switch (state) {
97                         case WIFI_AP_STATE_ENABLED -> {
98                             Slogf.i(TAG, "AP enabled successfully");
99                             synchronized (mLock) {
100                                 if (mSharedPreferences != null) {
101                                     Slogf.i(TAG,
102                                             "WIFI_AP_STATE_ENABLED received, saving state in "
103                                                     + "SharedPreferences store");
104                                     mSharedPreferences
105                                             .edit()
106                                             .putBoolean(
107                                                     KEY_PERSIST_TETHERING_ENABLED_LAST, /* value= */
108                                                     true)
109                                             .apply();
110                                 }
111                             }
112 
113                             // If the setting is enabled, tethering sessions should remain on even
114                             // if no devices are connected to it.
115                             if (mIsPersistTetheringCapabilitiesEnabled
116                                     && mIsPersistTetheringSettingEnabled) {
117                                 setSoftApAutoShutdownEnabled(/* enable= */ false);
118                             }
119                         }
120                         case WIFI_AP_STATE_DISABLED -> {
121                             synchronized (mLock) {
122                                 if (mSharedPreferences != null) {
123                                     Slogf.i(TAG,
124                                             "WIFI_AP_STATE_DISABLED received, saving state in "
125                                                     + "SharedPreferences store");
126                                     mSharedPreferences
127                                             .edit()
128                                             .putBoolean(
129                                                     KEY_PERSIST_TETHERING_ENABLED_LAST, /* value= */
130                                                     false)
131                                             .apply();
132                                 }
133                             }
134                         }
135                         case WIFI_AP_STATE_FAILED -> {
136                             Slogf.w(TAG, "WIFI_AP_STATE_FAILED state received");
137                             // FAILED state can occur during enabling OR disabling, should keep
138                             // previous setting within store.
139                         }
140                         default -> {
141                         }
142                     }
143                 }
144             };
145 
146     private final ContentObserver mPersistTetheringObserver =
147             new ContentObserver(mHandler) {
148                 @Override
149                 public void onChange(boolean selfChange) {
150                     Slogf.i(TAG, "%s setting has changed", ENABLE_PERSISTENT_TETHERING);
151                     // If the persist tethering setting is turned off, auto shutdown must be
152                     // re-enabled.
153                     boolean persistTetheringSettingEnabled =
154                             mFeatureFlags.persistApSettings() && TextUtils.equals("true",
155                                     Settings.Global.getString(mContext.getContentResolver(),
156                                             ENABLE_PERSISTENT_TETHERING));
157                     setSoftApAutoShutdownEnabled(!persistTetheringSettingEnabled);
158                 }
159             };
160 
161     @GuardedBy("mLock")
162     private SharedPreferences mSharedPreferences;
163 
CarWifiService(Context context)164     public CarWifiService(Context context) {
165         mContext = context;
166         mIsPersistTetheringCapabilitiesEnabled = context.getResources().getBoolean(
167                 R.bool.config_enablePersistTetheringCapabilities);
168         mIsPersistTetheringSettingEnabled = mFeatureFlags.persistApSettings() && TextUtils.equals(
169                 "true",
170                 Settings.Global.getString(context.getContentResolver(),
171                         ENABLE_PERSISTENT_TETHERING));
172         mWifiManager = context.getSystemService(WifiManager.class);
173         mTetheringManager = context.getSystemService(TetheringManager.class);
174         mCarPowerManagementService = CarLocalServices.getService(CarPowerManagementService.class);
175         mCarUserService = CarLocalServices.getService(CarUserService.class);
176     }
177 
178     @Override
init()179     public void init() {
180         if (!mIsPersistTetheringCapabilitiesEnabled) {
181             Slogf.w(TAG, "Persist tethering capability is not enabled");
182             return;
183         }
184 
185         // Listeners should only be set if there's capability (also allows for
186         // tethering persisting if setting is enabled, on next boot).
187         mWifiManager.registerSoftApCallback(mHandler::post, mSoftApCallback);
188         mCarUserService.runOnUser0Unlock(this::onSystemUserUnlocked);
189         mCarPowerManagementService.registerListener(mCarPowerStateListener);
190 
191         if (mFeatureFlags.persistApSettings()) {
192             mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
193                             ENABLE_PERSISTENT_TETHERING), /* notifyForDescendants= */ false,
194                     mPersistTetheringObserver);
195         }
196     }
197 
198     @Override
release()199     public void release() {
200         if (!mIsPersistTetheringCapabilitiesEnabled) {
201             Slogf.w(TAG, "Persist tethering capability is not enabled");
202             return;
203         }
204 
205         mWifiManager.unregisterSoftApCallback(mSoftApCallback);
206         mCarPowerManagementService.unregisterListener(mCarPowerStateListener);
207 
208         if (mFeatureFlags.persistApSettings()) {
209             mContext.getContentResolver().unregisterContentObserver(mPersistTetheringObserver);
210         }
211     }
212 
213     @Override
214     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)215     public void dumpProto(ProtoOutputStream proto) {
216         proto.write(CarWifiDumpProto.PERSIST_TETHERING_CAPABILITIES_ENABLED,
217                 mIsPersistTetheringCapabilitiesEnabled);
218         proto.write(CarWifiDumpProto.PERSIST_TETHERING_SETTING_ENABLED,
219                 mIsPersistTetheringSettingEnabled);
220         proto.write(CarWifiDumpProto.TETHERING_ENABLED, mWifiManager.isWifiApEnabled());
221         proto.write(CarWifiDumpProto.AUTO_SHUTDOWN_ENABLED,
222                 mWifiManager.getSoftApConfiguration().isAutoShutdownEnabled());
223     }
224 
225     @Override
226     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)227     public void dump(IndentingPrintWriter writer) {
228         writer.println("**CarWifiService**");
229         writer.println();
230         writer.println("Persist Tethering");
231         writer.println("mIsPersistTetheringCapabilitiesEnabled: "
232                 + mIsPersistTetheringCapabilitiesEnabled);
233         writer.println("mIsPersistTetheringSettingEnabled: " + mIsPersistTetheringSettingEnabled);
234         writer.println("Tethering enabled: " + mWifiManager.isWifiApEnabled());
235         writer.println("Auto shutdown enabled: "
236                 + mWifiManager.getSoftApConfiguration().isAutoShutdownEnabled());
237     }
238 
239     /**
240      * Returns {@code true} if the persist tethering settings are able to be changed.
241      */
242     @Override
canControlPersistTetheringSettings()243     public boolean canControlPersistTetheringSettings() {
244         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_READ_PERSIST_TETHERING_SETTINGS);
245         return mIsPersistTetheringCapabilitiesEnabled;
246     }
247 
248     /**
249      * Starts tethering if it's currently being persisted and was on last.
250      * Should only be called given that the SharedPreferences store is properly initialized.
251      */
startTethering()252     private void startTethering() {
253         if (!mIsPersistTetheringCapabilitiesEnabled || !mIsPersistTetheringSettingEnabled) {
254             return;
255         }
256 
257         if (mWifiManager.isWifiApEnabled()) {
258             return;
259         }
260 
261         synchronized (mLock) {
262             if (mSharedPreferences == null || !mSharedPreferences.getBoolean(
263                     KEY_PERSIST_TETHERING_ENABLED_LAST, /* defValue= */ false)) {
264                 Slogf.d(TAG, "Tethering was not enabled last");
265                 return;
266             }
267         }
268 
269         mTetheringManager.startTethering(TETHERING_WIFI, mHandler::post,
270                 new StartTetheringCallback() {
271                     @Override
272                     public void onTetheringFailed(int error) {
273                         Slogf.e(TAG, "Starting tethering failed: %d", error);
274                     }
275                 });
276     }
onStateOn()277     private void onStateOn() {
278         synchronized (mLock) {
279             if (mSharedPreferences == null) {
280                 Slogf.d(TAG, "SharedPreferences store has not been initialized");
281                 return;
282             }
283         }
284 
285         // User 0 has been unlocked, SharedPreferences is initialized and accessible.
286         startTethering();
287     }
288 
onSystemUserUnlocked()289     private void onSystemUserUnlocked() {
290         synchronized (mLock) {
291             // SharedPreferences are shared among different users thus only need initialized once.
292             // They should be initialized after user 0 is unlocked because SharedPreferences in
293             // credential encrypted storage are not available until after user 0 is unlocked.
294             mSharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME,
295                     Context.MODE_PRIVATE);
296         }
297 
298         if (mCarPowerManagementService.getPowerState() == CarPowerManager.STATE_ON) {
299             startTethering();
300         }
301     }
302 
setSoftApAutoShutdownEnabled(boolean enable)303     private void setSoftApAutoShutdownEnabled(boolean enable) {
304         SoftApConfiguration config = new SoftApConfiguration.Builder(
305                 mWifiManager.getSoftApConfiguration())
306                 .setAutoShutdownEnabled(enable)
307                 .build();
308         mWifiManager.setSoftApConfiguration(config);
309     }
310 }
311