1 /*
2  * Copyright (C) 2018 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;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.os.Looper;
24 import android.os.Process;
25 import android.os.SimpleClock;
26 import android.os.SystemClock;
27 
28 import androidx.annotation.VisibleForTesting;
29 import androidx.lifecycle.LifecycleObserver;
30 import androidx.lifecycle.OnLifecycleEvent;
31 import androidx.preference.PreferenceGroup;
32 import androidx.preference.PreferenceScreen;
33 
34 import com.android.settings.R;
35 import com.android.settings.core.SubSettingLauncher;
36 import com.android.settings.overlay.FeatureFactory;
37 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
38 import com.android.settingslib.core.AbstractPreferenceController;
39 import com.android.settingslib.core.lifecycle.Lifecycle;
40 import com.android.settingslib.wifi.WifiEntryPreference;
41 import com.android.wifitrackerlib.WifiEntry;
42 import com.android.wifitrackerlib.WifiPickerTracker;
43 
44 import java.time.Clock;
45 import java.time.ZoneOffset;
46 
47 // TODO(b/151133650): Replace AbstractPreferenceController with BasePreferenceController.
48 /**
49  * This places a preference into a PreferenceGroup owned by some parent
50  * controller class when there is a wifi connection present.
51  */
52 public class WifiConnectionPreferenceController extends AbstractPreferenceController implements
53         WifiPickerTracker.WifiPickerTrackerCallback, LifecycleObserver {
54 
55     private static final String TAG = "WifiConnPrefCtrl";
56 
57     private static final String KEY = "active_wifi_connection";
58 
59     // Max age of tracked WifiEntries.
60     private static final long MAX_SCAN_AGE_MILLIS = 15_000;
61     // Interval between initiating WifiPickerTracker scans.
62     private static final long SCAN_INTERVAL_MILLIS = 10_000;
63 
64     private UpdateListener mUpdateListener;
65     private Context mPrefContext;
66     private String mPreferenceGroupKey;
67     private PreferenceGroup mPreferenceGroup;
68     @VisibleForTesting
69     public WifiPickerTracker mWifiPickerTracker;
70     private WifiEntryPreference mPreference;
71     private int order;
72     private int mMetricsCategory;
73     // Worker thread used for WifiPickerTracker work.
74     private HandlerThread mWorkerThread;
75 
76     /**
77      * Used to notify a parent controller that this controller has changed in availability, or has
78      * updated the content in the preference that it manages.
79      */
80     public interface UpdateListener {
onChildrenUpdated()81         void onChildrenUpdated();
82     }
83 
84     /**
85      * @param context            the context for the UI where we're placing the preference
86      * @param lifecycle          for listening to lifecycle events for the UI
87      * @param updateListener     for notifying a parent controller of changes
88      * @param preferenceGroupKey the key to use to lookup the PreferenceGroup where this controller
89      *                           will add its preference
90      * @param order              the order that the preference added by this controller should use -
91      *                           useful when this preference needs to be ordered in a specific way
92      *                           relative to others in the PreferenceGroup
93      * @param metricsCategory    - the category to use as the source when handling the click on the
94      *                           pref to go to the wifi connection detail page
95      */
WifiConnectionPreferenceController(Context context, Lifecycle lifecycle, UpdateListener updateListener, String preferenceGroupKey, int order, int metricsCategory)96     public WifiConnectionPreferenceController(Context context, Lifecycle lifecycle,
97             UpdateListener updateListener, String preferenceGroupKey, int order,
98             int metricsCategory) {
99         super(context);
100         lifecycle.addObserver(this);
101         mUpdateListener = updateListener;
102         mPreferenceGroupKey = preferenceGroupKey;
103         this.order = order;
104         mMetricsCategory = metricsCategory;
105 
106         mWorkerThread = new HandlerThread(
107                 TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
108                 Process.THREAD_PRIORITY_BACKGROUND);
109         mWorkerThread.start();
110         final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
111             @Override
112             public long millis() {
113                 return SystemClock.elapsedRealtime();
114             }
115         };
116         mWifiPickerTracker = FeatureFactory.getFactory(context)
117                 .getWifiTrackerLibProvider()
118                 .createWifiPickerTracker(lifecycle, context,
119                         new Handler(Looper.getMainLooper()),
120                         mWorkerThread.getThreadHandler(),
121                         elapsedRealtimeClock,
122                         MAX_SCAN_AGE_MILLIS,
123                         SCAN_INTERVAL_MILLIS,
124                         this);
125     }
126 
127     /**
128      * This event is triggered when users click back button at 'Network & internet'.
129      */
130     @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
onDestroy()131     public void onDestroy() {
132         mWorkerThread.quit();
133     }
134 
135     @Override
isAvailable()136     public boolean isAvailable() {
137         return mWifiPickerTracker.getConnectedWifiEntry() != null;
138     }
139 
140     @Override
getPreferenceKey()141     public String getPreferenceKey() {
142         return KEY;
143     }
144 
145     @Override
displayPreference(PreferenceScreen screen)146     public void displayPreference(PreferenceScreen screen) {
147         super.displayPreference(screen);
148         mPreferenceGroup = screen.findPreference(mPreferenceGroupKey);
149         mPrefContext = screen.getContext();
150         update();
151     }
152 
updatePreference(WifiEntry wifiEntry)153     private void updatePreference(WifiEntry wifiEntry) {
154         if (mPreference != null) {
155             mPreferenceGroup.removePreference(mPreference);
156             mPreference = null;
157         }
158         if (wifiEntry == null || mPrefContext == null) {
159             return;
160         }
161 
162         mPreference = new WifiEntryPreference(mPrefContext, wifiEntry);
163         mPreference.setKey(KEY);
164         mPreference.refresh();
165         mPreference.setOrder(order);
166         mPreference.setOnPreferenceClickListener(pref -> {
167             final Bundle args = new Bundle();
168             args.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY,
169                     wifiEntry.getKey());
170             new SubSettingLauncher(mPrefContext)
171                     .setTitleRes(R.string.pref_title_network_details)
172                     .setDestination(WifiNetworkDetailsFragment.class.getName())
173                     .setArguments(args)
174                     .setSourceMetricsCategory(mMetricsCategory)
175                     .launch();
176             return true;
177         });
178         mPreferenceGroup.addPreference(mPreference);
179     }
180 
update()181     private void update() {
182         final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
183         if (connectedWifiEntry == null) {
184             updatePreference(null);
185         } else {
186             if (mPreference == null || !mPreference.getWifiEntry().equals(connectedWifiEntry)) {
187                 updatePreference(connectedWifiEntry);
188             } else if (mPreference != null) {
189                 mPreference.refresh();
190             }
191         }
192         mUpdateListener.onChildrenUpdated();
193     }
194 
195     /** Called when the state of Wifi has changed. */
196     @Override
onWifiStateChanged()197     public void onWifiStateChanged() {
198         update();
199     }
200 
201     /**
202      * Update the results when data changes.
203      */
204     @Override
onWifiEntriesChanged()205     public void onWifiEntriesChanged() {
206         update();
207     }
208 
209     @Override
onNumSavedSubscriptionsChanged()210     public void onNumSavedSubscriptionsChanged() {
211         // Do nothing.
212     }
213 
214     @Override
onNumSavedNetworksChanged()215     public void onNumSavedNetworksChanged() {
216         // Do nothing.
217     }
218 }
219