• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.connecteddevice.audiosharing;
18 
19 import static com.android.settingslib.bluetooth.BluetoothUtils.isBroadcasting;
20 
21 import android.app.settings.SettingsEnums;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.database.ContentObserver;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.provider.Settings;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.lifecycle.DefaultLifecycleObserver;
34 import androidx.lifecycle.LifecycleOwner;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.settings.R;
38 import com.android.settings.bluetooth.Utils;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settings.widget.ValidatedEditTextPreference;
42 import com.android.settingslib.bluetooth.BluetoothUtils;
43 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
44 import com.android.settingslib.bluetooth.LocalBluetoothManager;
45 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
46 import com.android.settingslib.utils.ThreadUtils;
47 
48 import java.nio.charset.StandardCharsets;
49 
50 public class AudioSharingPasswordPreferenceController extends BasePreferenceController
51         implements ValidatedEditTextPreference.Validator,
52                 AudioSharingPasswordPreference.OnDialogEventListener,
53                 DefaultLifecycleObserver {
54     private static final String TAG = "AudioSharingPasswordPreferenceController";
55     private static final String PREF_KEY = "audio_sharing_stream_password";
56     private static final String SHARED_PREF_NAME = "audio_sharing_settings";
57     private static final String SHARED_PREF_KEY = "default_password";
58     @Nullable private final ContentResolver mContentResolver;
59     @Nullable private final SharedPreferences mSharedPref;
60     @Nullable private final LocalBluetoothManager mBtManager;
61     @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
62     @Nullable private AudioSharingPasswordPreference mPreference;
63     private final ContentObserver mSettingsObserver;
64     private final SharedPreferences.OnSharedPreferenceChangeListener mSharedPrefChangeListener;
65     private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
66     private final MetricsFeatureProvider mMetricsFeatureProvider;
67 
AudioSharingPasswordPreferenceController(Context context, String preferenceKey)68     public AudioSharingPasswordPreferenceController(Context context, String preferenceKey) {
69         super(context, preferenceKey);
70         mBtManager = Utils.getLocalBluetoothManager(context);
71         mBroadcast =
72                 mBtManager != null
73                         ? mBtManager.getProfileManager().getLeAudioBroadcastProfile()
74                         : null;
75         mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
76         mContentResolver = context.getContentResolver();
77         mSettingsObserver = new PasswordSettingsObserver();
78         mSharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
79         mSharedPrefChangeListener = new PasswordSharedPrefChangeListener();
80         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
81     }
82 
83     @Override
onStart(@onNull LifecycleOwner owner)84     public void onStart(@NonNull LifecycleOwner owner) {
85         if (!isAvailable()) {
86             Log.d(TAG, "Feature is not available.");
87             return;
88         }
89         if (mContentResolver != null) {
90             mContentResolver.registerContentObserver(
91                     Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
92                     false,
93                     mSettingsObserver);
94         }
95         if (mSharedPref != null) {
96             mSharedPref.registerOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
97         }
98     }
99 
100     @Override
onStop(@onNull LifecycleOwner owner)101     public void onStop(@NonNull LifecycleOwner owner) {
102         if (!isAvailable()) {
103             Log.d(TAG, "Feature is not available.");
104             return;
105         }
106         if (mContentResolver != null) {
107             mContentResolver.unregisterContentObserver(mSettingsObserver);
108         }
109         if (mSharedPref != null) {
110             mSharedPref.unregisterOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
111         }
112     }
113 
114     @Override
getAvailabilityStatus()115     public int getAvailabilityStatus() {
116         return BluetoothUtils.isAudioSharingUIAvailable(mContext) ? AVAILABLE
117                 : UNSUPPORTED_ON_DEVICE;
118     }
119 
120     @Override
displayPreference(PreferenceScreen screen)121     public void displayPreference(PreferenceScreen screen) {
122         super.displayPreference(screen);
123         mPreference = screen.findPreference(getPreferenceKey());
124         if (mPreference != null) {
125             mPreference.setValidator(this);
126             mPreference.setIsPassword(true);
127             mPreference.setDialogLayoutResource(R.layout.audio_sharing_password_dialog);
128             mPreference.setOnDialogEventListener(this);
129             updatePreference();
130         }
131     }
132 
133     @Override
getPreferenceKey()134     public String getPreferenceKey() {
135         return PREF_KEY;
136     }
137 
138     @Override
isTextValid(String value)139     public boolean isTextValid(String value) {
140         boolean isValid = mAudioSharingPasswordValidator.isTextValid(value);
141         if (mPreference != null) {
142             mPreference.showEditTextFormatAlert(!isValid);
143         }
144         return isValid;
145     }
146 
147     @Override
onBindDialogView()148     public void onBindDialogView() {
149         if (mPreference == null || mBroadcast == null) {
150             return;
151         }
152         mPreference.setEditable(!isBroadcasting(mBtManager));
153         var password = mBroadcast.getBroadcastCode();
154         mPreference.setChecked(isPublicBroadcast(password));
155     }
156 
157     @Override
onPreferenceDataChanged(@onNull String password, boolean isPublicBroadcast)158     public void onPreferenceDataChanged(@NonNull String password, boolean isPublicBroadcast) {
159         var unused =
160                 ThreadUtils.postOnBackgroundThread(
161                         () -> {
162                             if (mBroadcast == null || isBroadcasting(mBtManager)) {
163                                 Log.w(
164                                         TAG,
165                                         "onPreferenceDataChanged() changing password when"
166                                                 + " broadcasting or null!");
167                                 return;
168                             }
169                             boolean isCurrentPublicBroadcast =
170                                     isPublicBroadcast(mBroadcast.getBroadcastCode());
171                             String currentDefaultPassword = getDefaultPassword(mContext);
172                             if (password.equals(currentDefaultPassword)
173                                     && isCurrentPublicBroadcast == isPublicBroadcast) {
174                                 Log.d(TAG, "onPreferenceDataChanged() nothing changed");
175                                 return;
176                             }
177                             persistDefaultPassword(mContext, password);
178                             mBroadcast.setBroadcastCode(
179                                     isPublicBroadcast ? new byte[0] : password.getBytes());
180                             mMetricsFeatureProvider.action(
181                                     mContext,
182                                     SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED,
183                                     isPublicBroadcast ? 1 : 0);
184                         });
185     }
186 
updatePreference()187     private void updatePreference() {
188         if (mBroadcast == null || mPreference == null) {
189             return;
190         }
191         var unused =
192                 ThreadUtils.postOnBackgroundThread(
193                         () -> {
194                             byte[] password = mBroadcast.getBroadcastCode();
195                             boolean noPassword = isPublicBroadcast(password);
196                             String passwordToDisplay =
197                                     noPassword
198                                             ? getDefaultPassword(mContext)
199                                             : new String(password, StandardCharsets.UTF_8);
200                             String passwordSummary =
201                                     noPassword
202                                             ? mContext.getString(
203                                                     R.string.audio_streams_no_password_summary)
204                                             : "********";
205 
206                             AudioSharingUtils.postOnMainThread(
207                                     mContext,
208                                     () -> {
209                                         // Check nullability to pass NullAway check
210                                         if (mPreference != null) {
211                                             mPreference.setText(passwordToDisplay);
212                                             mPreference.setSummary(passwordSummary);
213                                         }
214                                     });
215                         });
216     }
217 
218     private class PasswordSettingsObserver extends ContentObserver {
PasswordSettingsObserver()219         PasswordSettingsObserver() {
220             super(new Handler(Looper.getMainLooper()));
221         }
222 
223         @Override
onChange(boolean selfChange)224         public void onChange(boolean selfChange) {
225             Log.d(TAG, "onChange, broadcast password has been changed");
226             updatePreference();
227         }
228     }
229 
230     private class PasswordSharedPrefChangeListener
231             implements SharedPreferences.OnSharedPreferenceChangeListener {
232         @Override
onSharedPreferenceChanged( SharedPreferences sharedPreferences, @Nullable String key)233         public void onSharedPreferenceChanged(
234                 SharedPreferences sharedPreferences, @Nullable String key) {
235             if (!SHARED_PREF_KEY.equals(key)) {
236                 return;
237             }
238             Log.d(TAG, "onSharedPreferenceChanged, default password has been changed");
239             updatePreference();
240         }
241     }
242 
persistDefaultPassword(Context context, String defaultPassword)243     private void persistDefaultPassword(Context context, String defaultPassword) {
244         if (getDefaultPassword(context).equals(defaultPassword)) {
245             return;
246         }
247         if (mSharedPref == null) {
248             Log.w(TAG, "persistDefaultPassword(): sharedPref is empty!");
249             return;
250         }
251 
252         SharedPreferences.Editor editor = mSharedPref.edit();
253         editor.putString(SHARED_PREF_KEY, defaultPassword);
254         editor.apply();
255     }
256 
getDefaultPassword(Context context)257     private String getDefaultPassword(Context context) {
258         if (mSharedPref == null) {
259             Log.w(TAG, "getDefaultPassword(): sharedPref is empty!");
260             return "";
261         }
262 
263         String value = mSharedPref.getString(SHARED_PREF_KEY, "");
264         if (value != null && value.isEmpty()) {
265             Log.w(TAG, "getDefaultPassword(): default password is empty!");
266         }
267         return value;
268     }
269 
isPublicBroadcast(@ullable byte[] password)270     private static boolean isPublicBroadcast(@Nullable byte[] password) {
271         return password == null || password.length == 0;
272     }
273 }
274