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