1 /*
2  * Copyright (C) 2023 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 android.net.wifi.sharedconnectivity.service;
18 
19 import static android.Manifest.permission.NETWORK_SETTINGS;
20 import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.annotation.TestApi;
27 import android.app.Service;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
32 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
33 import android.net.wifi.sharedconnectivity.app.KnownNetwork;
34 import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
35 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
36 import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.RemoteCallbackList;
41 import android.os.RemoteException;
42 import android.util.Log;
43 
44 import com.android.internal.R;
45 
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.concurrent.CountDownLatch;
50 
51 
52 /**
53  * This class is the partly implemented service for injecting Shared Connectivity networks into the
54  * Wi-Fi Pickers and other relevant UI surfaces.
55  *
56  * Implementing application should extend this service and override the indicated methods.
57  * Callers to the service should use {@link SharedConnectivityManager} to bind to the implemented
58  * service as specified in the configuration overlay.
59  *
60  * @hide
61  */
62 @SystemApi
63 public abstract class SharedConnectivityService extends Service {
64     private static final String TAG = SharedConnectivityService.class.getSimpleName();
65     private static final boolean DEBUG = true;
66 
67     private Handler mHandler;
68     private final RemoteCallbackList<ISharedConnectivityCallback> mRemoteCallbackList =
69             new RemoteCallbackList<>();
70     private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
71     private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
72     private SharedConnectivitySettingsState mSettingsState = null;
73     private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
74             new HotspotNetworkConnectionStatus.Builder()
75                     .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
76                     .setExtras(Bundle.EMPTY).build();
77     private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus =
78             new KnownNetworkConnectionStatus.Builder()
79                     .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
80                     .setExtras(Bundle.EMPTY).build();
81     // Used for testing
82     private CountDownLatch mCountDownLatch;
83 
84     @Override
85     @Nullable
onBind(@onNull Intent intent)86     public final IBinder onBind(@NonNull Intent intent) {
87         if (DEBUG) Log.i(TAG, "onBind intent=" + intent);
88         mHandler = new Handler(getMainLooper());
89         IBinder serviceStub = new ISharedConnectivityService.Stub() {
90 
91             /**
92              * Registers a callback for receiving updates to the list of Hotspot Networks, Known
93              * Networks, shared connectivity settings state, hotspot network connection status and
94              * known network connection status.
95              *
96              * @param callback The callback of type {@link ISharedConnectivityCallback} to be called
97              *                 when there is update to the data.
98              */
99             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
100                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
101             @Override
102             public void registerCallback(ISharedConnectivityCallback callback) {
103                 checkPermissions();
104                 mHandler.post(() -> onRegisterCallback(callback));
105             }
106 
107             /**
108              * Unregisters a previously registered callback.
109              *
110              * @param callback The callback to unregister.
111              */
112             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
113                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
114             @Override
115             public void unregisterCallback(ISharedConnectivityCallback callback) {
116                 checkPermissions();
117                 mHandler.post(() -> onUnregisterCallback(callback));
118             }
119 
120             /**
121              * Connects to a hotspot network.
122              *
123              * @param network The network to connect to.
124              */
125             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
126                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
127             @Override
128             public void connectHotspotNetwork(HotspotNetwork network) {
129                 checkPermissions();
130                 mHandler.post(() -> onConnectHotspotNetwork(network));
131             }
132 
133             /**
134              * Disconnects from a previously connected hotspot network.
135              *
136              * @param network The network to disconnect from.
137              */
138             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
139                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
140             public void disconnectHotspotNetwork(HotspotNetwork network) {
141                 checkPermissions();
142                 mHandler.post(() -> onDisconnectHotspotNetwork(network));
143             }
144 
145             /**
146              * Adds a known network to the available networks on the device and connects to it.
147              *
148              * @param network The network to connect to.
149              */
150             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
151                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
152             @Override
153             public void connectKnownNetwork(KnownNetwork network) {
154                 checkPermissions();
155                 mHandler.post(() -> onConnectKnownNetwork(network));
156             }
157 
158             /**
159              * Removes a known network from the available networks on the device which will also
160              * disconnect the device from the network if it is connected to it.
161              *
162              * @param network The network to forget.
163              */
164             @Override
165             public void forgetKnownNetwork(KnownNetwork network) {
166                 checkPermissions();
167                 mHandler.post(() -> onForgetKnownNetwork(network));
168             }
169 
170             /**
171              * Gets the list of hotspot networks the user can select to connect to.
172              *
173              * @return Returns a {@link List} of {@link HotspotNetwork} objects
174              */
175             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
176                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
177             @Override
178             public List<HotspotNetwork> getHotspotNetworks() {
179                 checkPermissions();
180                 return mHotspotNetworks;
181             }
182 
183             /**
184              * Gets the list of known networks the user can select to connect to.
185              *
186              * @return Returns a {@link List} of {@link KnownNetwork} objects.
187              */
188             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
189                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
190             @Override
191             public List<KnownNetwork> getKnownNetworks() {
192                 checkPermissions();
193                 return mKnownNetworks;
194             }
195 
196             /**
197              * Gets the shared connectivity settings state.
198              *
199              * @return Returns a {@link SharedConnectivitySettingsState} object with the state.
200              */
201             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
202                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
203             @Override
204             public SharedConnectivitySettingsState getSettingsState() {
205                 checkPermissions();
206                 // Done lazily since creating it needs a context.
207                 if (mSettingsState == null) {
208                     mSettingsState = new SharedConnectivitySettingsState
209                             .Builder()
210                             .setInstantTetherEnabled(false)
211                             .setExtras(Bundle.EMPTY).build();
212                 }
213                 return mSettingsState;
214             }
215 
216             /**
217              * Gets the connection status of the hotspot network the user selected to connect to.
218              *
219              * @return Returns a {@link HotspotNetworkConnectionStatus} object with the connection
220              * status.
221              */
222             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
223                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
224             @Override
225             public HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus() {
226                 checkPermissions();
227                 return mHotspotNetworkConnectionStatus;
228             }
229 
230             /**
231              * Gets the connection status of the known network the user selected to connect to.
232              *
233              * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection
234              * status.
235              */
236             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
237                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
238             @Override
239             public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() {
240                 checkPermissions();
241                 return mKnownNetworkConnectionStatus;
242             }
243 
244             @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
245                     android.Manifest.permission.NETWORK_SETUP_WIZARD})
246             /**
247              * checkPermissions is using checkCallingOrSelfPermission to support CTS testing of this
248              * service. This does allow a process to bind to itself if it holds the proper
249              * permission. We do not consider this to be an issue given that the process can already
250              * access the service data since they are in the same process.
251              */
252             private void checkPermissions() {
253                 if (checkCallingOrSelfPermission(NETWORK_SETTINGS)
254                         != PackageManager.PERMISSION_GRANTED
255                         && checkCallingOrSelfPermission(NETWORK_SETUP_WIZARD)
256                         != PackageManager.PERMISSION_GRANTED) {
257                     throw new SecurityException("Calling process must have NETWORK_SETTINGS or"
258                             + " NETWORK_SETUP_WIZARD permission");
259                 }
260             }
261         };
262         onBind(); // For CTS testing
263         return serviceStub;
264     }
265 
266     /** @hide */
267     @TestApi
onBind()268     public void onBind() {
269     }
270 
271     /** @hide */
272     @TestApi
setCountdownLatch(@ullable CountDownLatch latch)273     public final void setCountdownLatch(@Nullable CountDownLatch latch) {
274         mCountDownLatch = latch;
275     }
276 
onRegisterCallback(ISharedConnectivityCallback callback)277     private void onRegisterCallback(ISharedConnectivityCallback callback) {
278         mRemoteCallbackList.register(callback);
279         try {
280             callback.onServiceConnected();
281         } catch (RemoteException e) {
282             if (DEBUG) Log.w(TAG, "Exception in onRegisterCallback", e);
283         }
284         if (mCountDownLatch != null) {
285             mCountDownLatch.countDown();
286         }
287     }
288 
onUnregisterCallback(ISharedConnectivityCallback callback)289     private void onUnregisterCallback(ISharedConnectivityCallback callback) {
290         mRemoteCallbackList.unregister(callback);
291         if (mCountDownLatch != null) {
292             mCountDownLatch.countDown();
293         }
294     }
295 
296     /**
297      * Implementing application should call this method to provide an up-to-date list of Hotspot
298      * Networks to be displayed to the user.
299      *
300      * This method updates the cached list and notifies all registered callbacks. Any callbacks that
301      * are inaccessible will be unregistered.
302      *
303      * @param networks The updated list of {@link HotspotNetwork} objects.
304      */
setHotspotNetworks(@onNull List<HotspotNetwork> networks)305     public final void setHotspotNetworks(@NonNull List<HotspotNetwork> networks) {
306         mHotspotNetworks = networks;
307 
308         int count = mRemoteCallbackList.beginBroadcast();
309         for (int i = 0; i < count; i++) {
310             try {
311                 mRemoteCallbackList.getBroadcastItem(i).onHotspotNetworksUpdated(mHotspotNetworks);
312             } catch (RemoteException e) {
313                 if (DEBUG) Log.w(TAG, "Exception in setHotspotNetworks", e);
314             }
315         }
316         mRemoteCallbackList.finishBroadcast();
317     }
318 
319     /**
320      * Implementing application should call this method to provide an up-to-date list of Known
321      * Networks to be displayed to the user.
322      *
323      * This method updates the cached list and notifies all registered callbacks. Any callbacks that
324      * are inaccessible will be unregistered.
325      *
326      * @param networks The updated list of {@link KnownNetwork} objects.
327      */
setKnownNetworks(@onNull List<KnownNetwork> networks)328     public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) {
329         mKnownNetworks = networks;
330 
331         int count = mRemoteCallbackList.beginBroadcast();
332         for (int i = 0; i < count; i++) {
333             try {
334                 mRemoteCallbackList.getBroadcastItem(i).onKnownNetworksUpdated(mKnownNetworks);
335             } catch (RemoteException e) {
336                 if (DEBUG) Log.w(TAG, "Exception in setKnownNetworks", e);
337             }
338         }
339         mRemoteCallbackList.finishBroadcast();
340     }
341 
342     /**
343      * Implementing application should call this method to provide an up-to-date state of Shared
344      * connectivity settings state.
345      *
346      * This method updates the cached state and notifies all registered callbacks. Any callbacks
347      * that are inaccessible will be unregistered.
348      *
349      * @param settingsState The updated state {@link SharedConnectivitySettingsState}
350      *                      objects.
351      */
setSettingsState(@onNull SharedConnectivitySettingsState settingsState)352     public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) {
353         mSettingsState = settingsState;
354 
355         int count = mRemoteCallbackList.beginBroadcast();
356         for (int i = 0; i < count; i++) {
357             try {
358                 mRemoteCallbackList.getBroadcastItem(i).onSharedConnectivitySettingsChanged(
359                         mSettingsState);
360             } catch (RemoteException e) {
361                 if (DEBUG) Log.w(TAG, "Exception in setSettingsState", e);
362             }
363         }
364         mRemoteCallbackList.finishBroadcast();
365     }
366 
367     /**
368      * Implementing application should call this method to provide an up-to-date status of enabling
369      * and connecting to the hotspot network.
370      *
371      * @param status The updated status {@link HotspotNetworkConnectionStatus} of the connection.
372      */
updateHotspotNetworkConnectionStatus( @onNull HotspotNetworkConnectionStatus status)373     public final void updateHotspotNetworkConnectionStatus(
374             @NonNull HotspotNetworkConnectionStatus status) {
375         mHotspotNetworkConnectionStatus = status;
376 
377         int count = mRemoteCallbackList.beginBroadcast();
378         for (int i = 0; i < count; i++) {
379             try {
380                 mRemoteCallbackList
381                         .getBroadcastItem(i).onHotspotNetworkConnectionStatusChanged(
382                                 mHotspotNetworkConnectionStatus);
383             } catch (RemoteException e) {
384                 if (DEBUG) Log.w(TAG, "Exception in updateHotspotNetworkConnectionStatus", e);
385             }
386         }
387         mRemoteCallbackList.finishBroadcast();
388     }
389 
390     /**
391      * Implementing application should call this method to provide an up-to-date status of
392      * connecting to a known network.
393      *
394      * @param status The updated status {@link KnownNetworkConnectionStatus} of the connection.
395      */
updateKnownNetworkConnectionStatus( @onNull KnownNetworkConnectionStatus status)396     public final void updateKnownNetworkConnectionStatus(
397             @NonNull KnownNetworkConnectionStatus status) {
398         mKnownNetworkConnectionStatus = status;
399 
400         int count = mRemoteCallbackList.beginBroadcast();
401         for (int i = 0; i < count; i++) {
402             try {
403                 mRemoteCallbackList
404                         .getBroadcastItem(i).onKnownNetworkConnectionStatusChanged(
405                                 mKnownNetworkConnectionStatus);
406             } catch (RemoteException e) {
407                 if (DEBUG) Log.w(TAG, "Exception in updateKnownNetworkConnectionStatus", e);
408             }
409         }
410         mRemoteCallbackList.finishBroadcast();
411     }
412 
413     /**
414      * System and settings UI support on the device for instant tether.
415      * @return True if the UI can display Instant Tether network data. False otherwise.
416      */
areHotspotNetworksEnabledForService(@onNull Context context)417     public static boolean areHotspotNetworksEnabledForService(@NonNull Context context) {
418         String servicePackage = context.getResources()
419                 .getString(R.string.config_sharedConnectivityServicePackage);
420         return Objects.equals(context.getPackageName(), servicePackage)
421                 && context.getResources()
422                         .getBoolean(R.bool.config_hotspotNetworksEnabledForService);
423     }
424 
425     /**
426      * System and settings UI support on the device for known networks.
427      * @return True if the UI can display known networks data. False otherwise.
428      */
areKnownNetworksEnabledForService(@onNull Context context)429     public static boolean areKnownNetworksEnabledForService(@NonNull Context context) {
430         String servicePackage = context.getResources()
431                 .getString(R.string.config_sharedConnectivityServicePackage);
432         return Objects.equals(context.getPackageName(), servicePackage)
433                 && context.getResources()
434                         .getBoolean(R.bool.config_knownNetworksEnabledForService);
435     }
436 
437     /**
438      * Implementing application should implement this method.
439      *
440      * Implementation should initiate a connection to the Hotspot Network indicated.
441      *
442      * @param network Object identifying the Hotspot Network the user has requested a connection to.
443      */
onConnectHotspotNetwork(@onNull HotspotNetwork network)444     public abstract void onConnectHotspotNetwork(@NonNull HotspotNetwork network);
445 
446     /**
447      * Implementing application should implement this method.
448      *
449      * Implementation should initiate a disconnection from the active Hotspot Network.
450      *
451      * @param network Object identifying the Hotspot Network the user has requested to disconnect.
452      */
onDisconnectHotspotNetwork(@onNull HotspotNetwork network)453     public abstract void onDisconnectHotspotNetwork(@NonNull HotspotNetwork network);
454 
455     /**
456      * Implementing application should implement this method.
457      *
458      * Implementation should initiate a connection to the Known Network indicated.
459      *
460      * @param network Object identifying the Known Network the user has requested a connection to.
461      */
onConnectKnownNetwork(@onNull KnownNetwork network)462     public abstract void onConnectKnownNetwork(@NonNull KnownNetwork network);
463 
464     /**
465      * Implementing application should implement this method.
466      *
467      * Implementation should remove the Known Network indicated from the synced list of networks.
468      *
469      * @param network Object identifying the Known Network the user has requested to forget.
470      */
onForgetKnownNetwork(@onNull KnownNetwork network)471     public abstract void onForgetKnownNetwork(@NonNull KnownNetwork network);
472 }
473