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 com.android.settings.wifi.repository;
18 
19 import android.app.ActivityOptions;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
23 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
24 import android.net.wifi.sharedconnectivity.app.KnownNetwork;
25 import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
26 import android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback;
27 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
28 import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
29 import android.os.Bundle;
30 import android.os.HandlerThread;
31 import android.provider.DeviceConfig;
32 import android.util.Log;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.VisibleForTesting;
36 import androidx.annotation.WorkerThread;
37 import androidx.lifecycle.LiveData;
38 import androidx.lifecycle.MutableLiveData;
39 
40 import com.android.settings.overlay.FeatureFactory;
41 
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * Shared Connectivity Repository for {@link SharedConnectivityManager}
47  */
48 public class SharedConnectivityRepository {
49     private static final String TAG = "SharedConnectivityRepository";
50     private static final String DEVICE_CONFIG_NAMESPACE = "wifi";
51     private static final String DEVICE_CONFIG_KEY = "shared_connectivity_enabled";
52 
53     private Context mAppContext;
54     private SharedConnectivityManager mManager;
55     private ClientCallback mClientCallback = new ClientCallback();
56     private HandlerThread mWorkerThread = new HandlerThread(TAG);
57     private Executor mWorkerExecutor = cmd -> mWorkerThread.getThreadHandler().post(cmd);
58     private Runnable mLaunchSettingsRunnable = () -> handleLaunchSettings();
59     @VisibleForTesting
60     MutableLiveData<SharedConnectivitySettingsState> mSettingsState = new MutableLiveData<>();
61 
SharedConnectivityRepository(@onNull Context appContext)62     public SharedConnectivityRepository(@NonNull Context appContext) {
63         this(appContext, isDeviceConfigEnabled());
64     }
65 
66     @VisibleForTesting
SharedConnectivityRepository(@onNull Context appContext, boolean isConfigEnabled)67     SharedConnectivityRepository(@NonNull Context appContext, boolean isConfigEnabled) {
68         mAppContext = appContext;
69         if (!isConfigEnabled) {
70             return;
71         }
72         mManager = mAppContext.getSystemService(SharedConnectivityManager.class);
73         if (mManager == null) {
74             Log.w(TAG, "Failed to get SharedConnectivityManager");
75             return;
76         }
77         mWorkerThread.start();
78         mManager.registerCallback(mWorkerExecutor, mClientCallback);
79     }
80 
81     /**
82      * Return whether Wi-Fi Shared Connectivity service is available or not.
83      *
84      * @return {@code true} if Wi-Fi Shared Connectivity service is available
85      */
isServiceAvailable()86     public boolean isServiceAvailable() {
87         return mManager != null;
88     }
89 
90     /**
91      * Gets SharedConnectivitySettingsState LiveData
92      */
getSettingsState()93     public LiveData<SharedConnectivitySettingsState> getSettingsState() {
94         return mSettingsState;
95     }
96 
97     /**
98      * Launch Instant Hotspot Settings
99      */
launchSettings()100     public void launchSettings() {
101         mWorkerExecutor.execute(mLaunchSettingsRunnable);
102     }
103 
104     @WorkerThread
105     @VisibleForTesting
handleLaunchSettings()106     void handleLaunchSettings() {
107         if (mManager == null) {
108             return;
109         }
110         SharedConnectivitySettingsState state = mManager.getSettingsState();
111         log("handleLaunchSettings(), state:" + state);
112         if (state == null) {
113             Log.e(TAG, "No SettingsState to launch Instant Hotspot settings");
114             return;
115         }
116         PendingIntent intent = state.getInstantTetherSettingsPendingIntent();
117         if (intent == null) {
118             Log.e(TAG, "No PendingIntent to launch Instant Hotspot settings");
119             return;
120         }
121         sendSettingsIntent(intent);
122     }
123 
124     @WorkerThread
125     @VisibleForTesting
sendSettingsIntent(@onNull PendingIntent intent)126     void sendSettingsIntent(@NonNull PendingIntent intent) {
127         try {
128             log("sendSettingsIntent(), sent intent:" + intent);
129             final Bundle options = ActivityOptions.makeBasic()
130                     .setPendingIntentBackgroundActivityStartMode(
131                             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
132                     .toBundle();
133             intent.send(options);
134         } catch (PendingIntent.CanceledException e) {
135             Log.e(TAG, "Failed to launch Instant Hotspot settings", e);
136         }
137     }
138 
139     @WorkerThread
140     class ClientCallback implements SharedConnectivityClientCallback {
141 
142         @Override
onHotspotNetworkConnectionStatusChanged(HotspotNetworkConnectionStatus status)143         public void onHotspotNetworkConnectionStatusChanged(HotspotNetworkConnectionStatus status) {
144             log("onHotspotNetworkConnectionStatusChanged(), status:" + status);
145         }
146 
147         @Override
onHotspotNetworksUpdated(List<HotspotNetwork> networks)148         public void onHotspotNetworksUpdated(List<HotspotNetwork> networks) {
149             log("onHotspotNetworksUpdated(), networks:" + networks);
150         }
151 
152         @Override
onKnownNetworkConnectionStatusChanged(KnownNetworkConnectionStatus status)153         public void onKnownNetworkConnectionStatusChanged(KnownNetworkConnectionStatus status) {
154             log("onKnownNetworkConnectionStatusChanged(), status:" + status);
155         }
156 
157         @Override
onKnownNetworksUpdated(List<KnownNetwork> networks)158         public void onKnownNetworksUpdated(List<KnownNetwork> networks) {
159             log("onKnownNetworksUpdated(), networks:" + networks);
160         }
161 
162         @Override
onRegisterCallbackFailed(Exception e)163         public void onRegisterCallbackFailed(Exception e) {
164             Log.e(TAG, "onRegisterCallbackFailed(), e:" + e);
165         }
166 
167         @Override
onServiceConnected()168         public void onServiceConnected() {
169             SharedConnectivitySettingsState state = mManager.getSettingsState();
170             Log.d(TAG, "onServiceConnected(), Manager#getSettingsState:" + state);
171             mSettingsState.postValue(state);
172         }
173 
174         @Override
onServiceDisconnected()175         public void onServiceDisconnected() {
176             log("onServiceDisconnected()");
177         }
178 
179         @Override
onSharedConnectivitySettingsChanged(SharedConnectivitySettingsState state)180         public void onSharedConnectivitySettingsChanged(SharedConnectivitySettingsState state) {
181             Log.d(TAG, "onSharedConnectivitySettingsChanged(), state:" + state);
182             mSettingsState.postValue(state);
183         }
184     }
185 
log(String msg)186     private void log(String msg) {
187         FeatureFactory.getFeatureFactory().getWifiFeatureProvider().verboseLog(TAG, msg);
188     }
189 
190     /**
191      * Returns true if Shared Connectivity feature is enabled.
192      */
isDeviceConfigEnabled()193     public static boolean isDeviceConfigEnabled() {
194         return DeviceConfig.getBoolean(DEVICE_CONFIG_NAMESPACE, DEVICE_CONFIG_KEY, false);
195     }
196 }
197