1 /*
2  * Copyright (C) 2014 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.systemui.statusbar.connectivity;
18 
19 import android.content.Intent;
20 import android.os.UserHandle;
21 import android.os.UserManager;
22 import android.provider.Settings;
23 import android.util.IndentingPrintWriter;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.lifecycle.Lifecycle;
29 import androidx.lifecycle.LifecycleOwner;
30 import androidx.lifecycle.LifecycleRegistry;
31 
32 import com.android.systemui.settings.UserTracker;
33 import com.android.wifitrackerlib.MergedCarrierEntry;
34 import com.android.wifitrackerlib.WifiEntry;
35 import com.android.wifitrackerlib.WifiPickerTracker;
36 
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.concurrent.Executor;
43 
44 /** */
45 public class AccessPointControllerImpl implements AccessPointController,
46         WifiPickerTracker.WifiPickerTrackerCallback,
47         LifecycleOwner {
48     private static final String TAG = "AccessPointController";
49     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
50 
51     // This string extra specifies a network to open the connect dialog on, so the user can enter
52     // network credentials.  This is used by quick settings for secured networks.
53     private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
54 
55     private static final int[] ICONS = WifiIcons.WIFI_FULL_ICONS;
56 
57     private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
58     private final UserManager mUserManager;
59     private final UserTracker mUserTracker;
60     private final Executor mMainExecutor;
61 
62     private @Nullable WifiPickerTracker mWifiPickerTracker;
63     private WifiPickerTrackerFactory mWifiPickerTrackerFactory;
64 
65     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
66 
67     private int mCurrentUser;
68 
AccessPointControllerImpl( UserManager userManager, UserTracker userTracker, Executor mainExecutor, WifiPickerTrackerFactory wifiPickerTrackerFactory )69     public AccessPointControllerImpl(
70             UserManager userManager,
71             UserTracker userTracker,
72             Executor mainExecutor,
73             WifiPickerTrackerFactory wifiPickerTrackerFactory
74     ) {
75         mUserManager = userManager;
76         mUserTracker = userTracker;
77         mCurrentUser = userTracker.getUserId();
78         mMainExecutor = mainExecutor;
79         mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
80         mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
81     }
82 
83     /**
84      * Initializes the controller.
85      *
86      * Will create a WifiPickerTracker associated to this controller.
87      */
init()88     public void init() {
89         if (mWifiPickerTracker == null) {
90             mWifiPickerTracker = mWifiPickerTrackerFactory.create(this.getLifecycle(), this, TAG);
91         }
92     }
93 
94     @NonNull
95     @Override
getLifecycle()96     public Lifecycle getLifecycle() {
97         return mLifecycle;
98     }
99 
100     @Override
finalize()101     protected void finalize() throws Throwable {
102         mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.DESTROYED));
103         super.finalize();
104     }
105 
canConfigWifi()106     public boolean canConfigWifi() {
107         if (!mWifiPickerTrackerFactory.isSupported()) return false;
108         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
109                 new UserHandle(mCurrentUser));
110     }
111 
canConfigMobileData()112     public boolean canConfigMobileData() {
113         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
114                 UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
115     }
116 
onUserSwitched(int newUserId)117     void onUserSwitched(int newUserId) {
118         mCurrentUser = newUserId;
119     }
120 
121     @Override
addAccessPointCallback(AccessPointCallback callback)122     public void addAccessPointCallback(AccessPointCallback callback) {
123         if (callback == null || mCallbacks.contains(callback)) return;
124         if (DEBUG) Log.d(TAG, "addCallback " + callback);
125         mCallbacks.add(callback);
126         if (mCallbacks.size() == 1) {
127             mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
128         }
129     }
130 
131     @Override
removeAccessPointCallback(AccessPointCallback callback)132     public void removeAccessPointCallback(AccessPointCallback callback) {
133         if (callback == null) return;
134         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
135         mCallbacks.remove(callback);
136         if (mCallbacks.isEmpty()) {
137             mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
138         }
139     }
140 
141     @Override
scanForAccessPoints()142     public void scanForAccessPoints() {
143         if (mWifiPickerTracker == null) {
144             fireAccessPointsCallback(Collections.emptyList());
145             return;
146         }
147         List<WifiEntry> entries = mWifiPickerTracker.getWifiEntries();
148         WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry();
149         if (connectedEntry != null) {
150             entries.add(0, connectedEntry);
151         }
152         fireAccessPointsCallback(entries);
153     }
154 
155     @Override
getMergedCarrierEntry()156     public MergedCarrierEntry getMergedCarrierEntry() {
157         if (mWifiPickerTracker == null) {
158             fireAccessPointsCallback(Collections.emptyList());
159             return null;
160         }
161         return mWifiPickerTracker.getMergedCarrierEntry();
162     }
163 
164     @Override
getIcon(WifiEntry ap)165     public int getIcon(WifiEntry ap) {
166         int level = ap.getLevel();
167         return ICONS[Math.max(0, level)];
168     }
169 
170     /**
171      * Connects to a {@link WifiEntry} if it's saved or does not require security.
172      *
173      * If the entry is not saved and requires security, will trigger
174      * {@link AccessPointCallback#onSettingsActivityTriggered}.
175      * @param ap
176      * @return {@code true} if {@link AccessPointCallback#onSettingsActivityTriggered} is triggered
177      */
connect(@ullable WifiEntry ap)178     public boolean connect(@Nullable WifiEntry ap) {
179         if (ap == null) return false;
180         if (DEBUG) {
181             if (ap.getWifiConfiguration() != null) {
182                 Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
183             } else {
184                 Log.d(TAG, "connect to unsaved network " + ap.getTitle());
185             }
186         }
187         if (ap.isSaved()) {
188             ap.connect(mConnectCallback);
189         } else {
190             // Unknown network, need to add it.
191             if (ap.getSecurity() != WifiEntry.SECURITY_NONE) {
192                 Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
193                 intent.putExtra(EXTRA_START_CONNECT_SSID, ap.getSsid());
194                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
195                 fireSettingsIntentCallback(intent);
196                 return true;
197             } else {
198                 ap.connect(mConnectCallback);
199             }
200         }
201         return false;
202     }
203 
fireSettingsIntentCallback(Intent intent)204     private void fireSettingsIntentCallback(Intent intent) {
205         for (AccessPointCallback callback : mCallbacks) {
206             callback.onSettingsActivityTriggered(intent);
207         }
208     }
209 
fireAccessPointsCallback(List<WifiEntry> aps)210     private void fireAccessPointsCallback(List<WifiEntry> aps) {
211         for (AccessPointCallback callback : mCallbacks) {
212             callback.onAccessPointsChanged(aps);
213         }
214     }
215 
fireWifiScanCallback(boolean isScan)216     private void fireWifiScanCallback(boolean isScan) {
217         for (AccessPointCallback callback : mCallbacks) {
218             callback.onWifiScan(isScan);
219         }
220     }
221 
dump(PrintWriter pw)222     void dump(PrintWriter pw) {
223         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
224         ipw.println("AccessPointControllerImpl:");
225         ipw.increaseIndent();
226         ipw.println("Callbacks: " + Arrays.toString(mCallbacks.toArray()));
227         ipw.println("WifiPickerTracker: " + mWifiPickerTracker.toString());
228         if (mWifiPickerTracker != null && !mCallbacks.isEmpty()) {
229             ipw.println("Connected: " + mWifiPickerTracker.getConnectedWifiEntry());
230             ipw.println("Other wifi entries: "
231                     + Arrays.toString(mWifiPickerTracker.getWifiEntries().toArray()));
232         } else if (mWifiPickerTracker != null) {
233             ipw.println("WifiPickerTracker not started, cannot get reliable entries");
234         }
235         ipw.decreaseIndent();
236     }
237 
238     @Override
onWifiStateChanged()239     public void onWifiStateChanged() {
240         scanForAccessPoints();
241     }
242 
243     @Override
onWifiEntriesChanged()244     public void onWifiEntriesChanged() {
245         scanForAccessPoints();
246     }
247 
248     @Override
onWifiEntriesChanged(@ifiPickerTracker.WifiEntriesChangedReason int reason)249     public void onWifiEntriesChanged(@WifiPickerTracker.WifiEntriesChangedReason int reason) {
250         onWifiEntriesChanged();
251         if (reason == WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) {
252             fireWifiScanCallback(false /* isScan */);
253         }
254     }
255 
256     @Override
onNumSavedNetworksChanged()257     public void onNumSavedNetworksChanged() {
258         // Do nothing
259     }
260 
261     @Override
onNumSavedSubscriptionsChanged()262     public void onNumSavedSubscriptionsChanged() {
263         // Do nothing
264     }
265 
266     @Override
onScanRequested()267     public void onScanRequested() {
268         fireWifiScanCallback(true /* isScan */);
269     }
270 
271     private final WifiEntry.ConnectCallback mConnectCallback = new WifiEntry.ConnectCallback() {
272         @Override
273         public void onConnectResult(int status) {
274             if (status == CONNECT_STATUS_SUCCESS) {
275                 if (DEBUG) Log.d(TAG, "connect success");
276             } else {
277                 if (DEBUG) Log.d(TAG, "connect failure reason=" + status);
278             }
279         }
280     };
281 }
282