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