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.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
20 
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.SharedPreferences;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.preference.PreferenceScreen;
29 
30 import com.android.settings.R;
31 import com.android.settings.bluetooth.Utils;
32 import com.android.settings.core.BasePreferenceController;
33 import com.android.settings.overlay.FeatureFactory;
34 import com.android.settings.widget.ValidatedEditTextPreference;
35 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
36 import com.android.settingslib.bluetooth.LocalBluetoothManager;
37 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
38 import com.android.settingslib.utils.ThreadUtils;
39 
40 import java.nio.charset.StandardCharsets;
41 
42 public class AudioSharingPasswordPreferenceController extends BasePreferenceController
43         implements ValidatedEditTextPreference.Validator,
44                 AudioSharingPasswordPreference.OnDialogEventListener {
45 
46     private static final String TAG = "AudioSharingPasswordPreferenceController";
47     private static final String PREF_KEY = "audio_sharing_stream_password";
48     private static final String SHARED_PREF_NAME = "audio_sharing_settings";
49     private static final String SHARED_PREF_KEY = "default_password";
50     @Nullable private final LocalBluetoothManager mBtManager;
51     @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
52     @Nullable private AudioSharingPasswordPreference mPreference;
53     private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
54     private final MetricsFeatureProvider mMetricsFeatureProvider;
55 
AudioSharingPasswordPreferenceController(Context context, String preferenceKey)56     public AudioSharingPasswordPreferenceController(Context context, String preferenceKey) {
57         super(context, preferenceKey);
58         mBtManager = Utils.getLocalBluetoothManager(context);
59         mBroadcast =
60                 mBtManager != null
61                         ? mBtManager.getProfileManager().getLeAudioBroadcastProfile()
62                         : null;
63         mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
64         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
65     }
66 
67     @Override
getAvailabilityStatus()68     public int getAvailabilityStatus() {
69         return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
70     }
71 
72     @Override
displayPreference(PreferenceScreen screen)73     public void displayPreference(PreferenceScreen screen) {
74         super.displayPreference(screen);
75         mPreference = screen.findPreference(getPreferenceKey());
76         if (mPreference != null) {
77             mPreference.setValidator(this);
78             mPreference.setIsPassword(true);
79             mPreference.setDialogLayoutResource(R.layout.audio_sharing_password_dialog);
80             mPreference.setOnDialogEventListener(this);
81             updatePreference();
82         }
83     }
84 
85     @Override
getPreferenceKey()86     public String getPreferenceKey() {
87         return PREF_KEY;
88     }
89 
90     @Override
isTextValid(String value)91     public boolean isTextValid(String value) {
92         return mAudioSharingPasswordValidator.isTextValid(value);
93     }
94 
95     @Override
onBindDialogView()96     public void onBindDialogView() {
97         if (mPreference == null || mBroadcast == null) {
98             return;
99         }
100         mPreference.setEditable(!isBroadcasting(mBtManager));
101         var password = mBroadcast.getBroadcastCode();
102         mPreference.setChecked(isPublicBroadcast(password));
103     }
104 
105     @Override
onPreferenceDataChanged(@onNull String password, boolean isPublicBroadcast)106     public void onPreferenceDataChanged(@NonNull String password, boolean isPublicBroadcast) {
107         var unused =
108                 ThreadUtils.postOnBackgroundThread(
109                         () -> {
110                             if (mBroadcast == null || isBroadcasting(mBtManager)) {
111                                 Log.w(
112                                         TAG,
113                                         "onPreferenceDataChanged() changing password when"
114                                                 + " broadcasting or null!");
115                                 return;
116                             }
117                             boolean isCurrentPublicBroadcast =
118                                     isPublicBroadcast(mBroadcast.getBroadcastCode());
119                             String currentDefaultPassword = getDefaultPassword(mContext);
120                             if (password.equals(currentDefaultPassword)
121                                     && isCurrentPublicBroadcast == isPublicBroadcast) {
122                                 Log.d(TAG, "onPreferenceDataChanged() nothing changed");
123                                 return;
124                             }
125                             persistDefaultPassword(mContext, password);
126                             mBroadcast.setBroadcastCode(
127                                     isPublicBroadcast ? new byte[0] : password.getBytes());
128                             updatePreference();
129                             mMetricsFeatureProvider.action(
130                                     mContext,
131                                     SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED,
132                                     isPublicBroadcast ? 1 : 0);
133                         });
134     }
135 
updatePreference()136     private void updatePreference() {
137         if (mBroadcast == null || mPreference == null) {
138             return;
139         }
140         var unused =
141                 ThreadUtils.postOnBackgroundThread(
142                         () -> {
143                             byte[] password = mBroadcast.getBroadcastCode();
144                             boolean noPassword = isPublicBroadcast(password);
145                             String passwordToDisplay =
146                                     noPassword
147                                             ? getDefaultPassword(mContext)
148                                             : new String(password, StandardCharsets.UTF_8);
149                             String passwordSummary =
150                                     noPassword
151                                             ? mContext.getString(
152                                                     R.string.audio_streams_no_password_summary)
153                                             : "********";
154 
155                             AudioSharingUtils.postOnMainThread(
156                                     mContext,
157                                     () -> {
158                                         // Check nullability to pass NullAway check
159                                         if (mPreference != null) {
160                                             mPreference.setText(passwordToDisplay);
161                                             mPreference.setSummary(passwordSummary);
162                                         }
163                                     });
164                         });
165     }
166 
persistDefaultPassword(Context context, String defaultPassword)167     private static void persistDefaultPassword(Context context, String defaultPassword) {
168         if (getDefaultPassword(context).equals(defaultPassword)) {
169             return;
170         }
171 
172         SharedPreferences sharedPref =
173                 context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
174         if (sharedPref == null) {
175             Log.w(TAG, "persistDefaultPassword(): sharedPref is empty!");
176             return;
177         }
178 
179         SharedPreferences.Editor editor = sharedPref.edit();
180         editor.putString(SHARED_PREF_KEY, defaultPassword);
181         editor.apply();
182     }
183 
getDefaultPassword(Context context)184     private static String getDefaultPassword(Context context) {
185         SharedPreferences sharedPref =
186                 context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
187         if (sharedPref == null) {
188             Log.w(TAG, "getDefaultPassword(): sharedPref is empty!");
189             return "";
190         }
191 
192         String value = sharedPref.getString(SHARED_PREF_KEY, "");
193         if (value != null && value.isEmpty()) {
194             Log.w(TAG, "getDefaultPassword(): default password is empty!");
195         }
196         return value;
197     }
198 
isPublicBroadcast(@ullable byte[] password)199     private static boolean isPublicBroadcast(@Nullable byte[] password) {
200         return password == null || password.length == 0;
201     }
202 }
203