Update DialogFragment UI when BT device is add/remove/rename

* Add AudioSwitchCallback() in AudioSwitchPreferenceController.
  This callback is used to notify SoudSettings to update the dialogFragment UI.
* Add UpdatableListPreferenceDialogFragment that updates the available
  options when dialog is shown
* Add test to verify the adapter count when
  onListPreferenceUpdated() is called.

Bug: 77783217
Test: make -j50 RunSettingsRoboTests
Change-Id: I8cac1b30ec50df026f4b7722dd1cd2f69e77a4cb
Merged-In: I8cac1b30ec50df026f4b7722dd1cd2f69e77a4cb
This commit is contained in:
hughchen
2018-04-23 20:13:14 +08:00
committed by Hugh Chen
parent 0cf2e41bf8
commit b6ac12eed4
6 changed files with 316 additions and 2 deletions

View File

@@ -26,16 +26,22 @@ import android.os.UserHandle;
import android.preference.SeekBarVolumizer; import android.preference.SeekBarVolumizer;
import android.provider.SearchIndexableResource; import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.text.TextUtils; import android.text.TextUtils;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.RingtonePreference; import com.android.settings.RingtonePreference;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.sound.HandsFreeProfileOutputPreferenceController;
import com.android.settings.sound.MediaOutputPreferenceController;
import com.android.settings.widget.PreferenceCategoryController; import com.android.settings.widget.PreferenceCategoryController;
import com.android.settings.widget.UpdatableListPreferenceDialogFragment;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList; import java.util.ArrayList;
@@ -68,6 +74,9 @@ public class SoundSettings extends DashboardFragment {
}; };
private RingtonePreference mRequestPreference; private RingtonePreference mRequestPreference;
private UpdatableListPreferenceDialogFragment mDialogFragment;
private String mMediaOutputControllerKey;
private String mHfpOutputControllerKey;
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
@@ -82,6 +91,11 @@ public class SoundSettings extends DashboardFragment {
if (!TextUtils.isEmpty(selectedPreference)) { if (!TextUtils.isEmpty(selectedPreference)) {
mRequestPreference = (RingtonePreference) findPreference(selectedPreference); mRequestPreference = (RingtonePreference) findPreference(selectedPreference);
} }
UpdatableListPreferenceDialogFragment dialogFragment =
(UpdatableListPreferenceDialogFragment) getFragmentManager()
.findFragmentByTag(TAG);
mDialogFragment = dialogFragment;
} }
} }
@@ -111,6 +125,23 @@ public class SoundSettings extends DashboardFragment {
return super.onPreferenceTreeClick(preference); return super.onPreferenceTreeClick(preference);
} }
@Override
public void onDisplayPreferenceDialog(Preference preference) {
final int metricsCategory;
if (mHfpOutputControllerKey.equals(preference.getKey())) {
metricsCategory = MetricsProto.MetricsEvent.DIALOG_SWITCH_HFP_DEVICES;
} else if (mMediaOutputControllerKey.equals(preference.getKey())) {
metricsCategory = MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES;
} else {
metricsCategory = Instrumentable.METRICS_CATEGORY_UNKNOWN;
}
mDialogFragment = UpdatableListPreferenceDialogFragment.
newInstance(preference.getKey(), metricsCategory);
mDialogFragment.setTargetFragment(this, 0);
mDialogFragment.show(getFragmentManager(), TAG);
}
@Override @Override
protected String getLogTag() { protected String getLogTag() {
return TAG; return TAG;
@@ -152,6 +183,14 @@ public class SoundSettings extends DashboardFragment {
volumeControllers.add(use(NotificationVolumePreferenceController.class)); volumeControllers.add(use(NotificationVolumePreferenceController.class));
volumeControllers.add(use(CallVolumePreferenceController.class)); volumeControllers.add(use(CallVolumePreferenceController.class));
use(MediaOutputPreferenceController.class).setCallback(listPreference ->
onPreferenceDataChanged(listPreference));
mMediaOutputControllerKey = use(MediaOutputPreferenceController.class).getPreferenceKey();
use(HandsFreeProfileOutputPreferenceController.class).setCallback(listPreference ->
onPreferenceDataChanged(listPreference));
mHfpOutputControllerKey =
use(HandsFreeProfileOutputPreferenceController.class).getPreferenceKey();
for (VolumeSeekBarPreferenceController controller : volumeControllers) { for (VolumeSeekBarPreferenceController controller : volumeControllers) {
controller.setCallback(mVolumeCallback); controller.setCallback(mVolumeCallback);
getLifecycle().addObserver(controller); getLifecycle().addObserver(controller);
@@ -287,4 +326,10 @@ public class SoundSettings extends DashboardFragment {
workSoundController.enableWorkSync(); workSoundController.enableWorkSync();
} }
} }
}
private void onPreferenceDataChanged(ListPreference preference) {
if (mDialogFragment != null) {
mDialogFragment.onListPreferenceUpdated(preference);
}
}
}

View File

@@ -78,6 +78,7 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
protected final LocalBluetoothProfileManager mProfileManager; protected final LocalBluetoothProfileManager mProfileManager;
protected int mSelectedIndex; protected int mSelectedIndex;
protected Preference mPreference; protected Preference mPreference;
protected AudioSwitchCallback mAudioSwitchPreferenceCallback;
private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
private final LocalBluetoothManager mLocalBluetoothManager; private final LocalBluetoothManager mLocalBluetoothManager;
@@ -85,6 +86,10 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
private final WiredHeadsetBroadcastReceiver mReceiver; private final WiredHeadsetBroadcastReceiver mReceiver;
private final Handler mHandler; private final Handler mHandler;
public interface AudioSwitchCallback {
void onPreferenceDataChanged(ListPreference preference);
}
public AudioSwitchPreferenceController(Context context, String preferenceKey) { public AudioSwitchPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey); super(context, preferenceKey);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -207,6 +212,10 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
} }
public void setCallback(AudioSwitchCallback callback) {
mAudioSwitchPreferenceCallback = callback;
}
protected boolean isStreamFromOutputDevice(int streamType, int device) { protected boolean isStreamFromOutputDevice(int streamType, int device) {
return (device & mAudioManager.getDevicesForStream(streamType)) != 0; return (device & mAudioManager.getDevicesForStream(streamType)) != 0;
} }
@@ -335,6 +344,7 @@ public abstract class AudioSwitchPreferenceController extends BasePreferenceCont
listPreference.setEntryValues(mediaValues); listPreference.setEntryValues(mediaValues);
listPreference.setValueIndex(mSelectedIndex); listPreference.setValueIndex(mSelectedIndex);
listPreference.setSummary(mediaOutputs[mSelectedIndex]); listPreference.setSummary(mediaOutputs[mSelectedIndex]);
mAudioSwitchPreferenceCallback.onPreferenceDataChanged(listPreference);
} }
private int getConnectedDeviceIndex(String hardwareAddress) { private int getConnectedDeviceIndex(String hardwareAddress) {

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.widget;
import android.app.AlertDialog;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceDialogFragment;
import android.support.v7.preference.ListPreference;
import android.widget.ArrayAdapter;
import com.android.settingslib.core.instrumentation.Instrumentable;
import java.util.ArrayList;
/**
* {@link PreferenceDialogFragment} that updates the available options
* when {@code onListPreferenceUpdated} is called."
*/
public class UpdatableListPreferenceDialogFragment extends PreferenceDialogFragment implements
Instrumentable {
private static final String SAVE_STATE_INDEX = "UpdatableListPreferenceDialogFragment.index";
private static final String SAVE_STATE_ENTRIES =
"UpdatableListPreferenceDialogFragment.entries";
private static final String SAVE_STATE_ENTRY_VALUES =
"UpdatableListPreferenceDialogFragment.entryValues";
private static final String METRICS_CATEGORY_KEY = "metrics_category_key";
private ArrayAdapter mAdapter;
private int mClickedDialogEntryIndex;
private ArrayList<CharSequence> mEntries;
private CharSequence[] mEntryValues;
private int mMetricsCategory = Instrumentable.METRICS_CATEGORY_UNKNOWN;
public static UpdatableListPreferenceDialogFragment newInstance(
String key, int metricsCategory) {
UpdatableListPreferenceDialogFragment fragment =
new UpdatableListPreferenceDialogFragment();
Bundle args = new Bundle(1);
args.putString(ARG_KEY, key);
args.putInt(METRICS_CATEGORY_KEY, metricsCategory);
fragment.setArguments(args);
return fragment;
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle bundle = getArguments();
mMetricsCategory =
bundle.getInt(METRICS_CATEGORY_KEY, Instrumentable.METRICS_CATEGORY_UNKNOWN);
if (savedInstanceState == null) {
mEntries = new ArrayList<>();
setPreferenceData(getListPreference());
} else {
mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
mEntries = savedInstanceState.getCharSequenceArrayList(SAVE_STATE_ENTRIES);
mEntryValues =
savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
outState.putCharSequenceArrayList(SAVE_STATE_ENTRIES, mEntries);
outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
}
@Override
public void onDialogClosed(boolean positiveResult) {
final ListPreference preference = getListPreference();
if (positiveResult && mClickedDialogEntryIndex >= 0) {
final String value = mEntryValues[mClickedDialogEntryIndex].toString();
if (preference.callChangeListener(value)) {
preference.setValue(value);
}
}
}
@VisibleForTesting
void setAdapter(ArrayAdapter adapter) {
mAdapter = adapter;
}
@VisibleForTesting
void setEntries(ArrayList<CharSequence> entries) {
mEntries = entries;
}
@VisibleForTesting
ArrayAdapter getAdapter() {
return mAdapter;
}
@VisibleForTesting
void setMetricsCategory(Bundle bundle) {
mMetricsCategory =
bundle.getInt(METRICS_CATEGORY_KEY, Instrumentable.METRICS_CATEGORY_UNKNOWN);
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
final TypedArray a = getContext().obtainStyledAttributes(
null,
com.android.internal.R.styleable.AlertDialog,
com.android.internal.R.attr.alertDialogStyle, 0);
mAdapter = new ArrayAdapter<>(
getContext(),
a.getResourceId(
com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
com.android.internal.R.layout.select_dialog_singlechoice),
mEntries);
builder.setSingleChoiceItems(mAdapter, mClickedDialogEntryIndex,
(dialog, which) -> {
mClickedDialogEntryIndex = which;
onClick(dialog, -1);
dialog.dismiss();
});
builder.setPositiveButton(null, null);
a.recycle();
}
@Override
public int getMetricsCategory() {
return mMetricsCategory;
}
private ListPreference getListPreference() {
return (ListPreference) getPreference();
}
private void setPreferenceData(ListPreference preference) {
mEntries.clear();
mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
for (CharSequence entry : preference.getEntries()) {
mEntries.add(entry);
}
mEntryValues = preference.getEntryValues();
}
public void onListPreferenceUpdated(ListPreference preference) {
if (mAdapter != null) {
setPreferenceData(preference);
mAdapter.notifyDataSetChanged();
}
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.sound; package com.android.settings.sound;
import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
import static android.media.AudioSystem.DEVICE_OUT_USB_HEADSET; import static android.media.AudioSystem.DEVICE_OUT_USB_HEADSET;
@@ -93,6 +92,8 @@ public class HandsFreeProfileOutputPreferenceControllerTest {
private HeadsetProfile mHeadsetProfile; private HeadsetProfile mHeadsetProfile;
@Mock @Mock
private HearingAidProfile mHearingAidProfile; private HearingAidProfile mHearingAidProfile;
@Mock
private AudioSwitchPreferenceController.AudioSwitchCallback mAudioSwitchPreferenceCallback;
private Context mContext; private Context mContext;
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
@@ -156,6 +157,7 @@ public class HandsFreeProfileOutputPreferenceControllerTest {
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mScreen.addPreference(mPreference); mScreen.addPreference(mPreference);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.setCallback(mAudioSwitchPreferenceCallback);
} }
@After @After

View File

@@ -94,6 +94,8 @@ public class MediaOutputPreferenceControllerTest {
private A2dpProfile mA2dpProfile; private A2dpProfile mA2dpProfile;
@Mock @Mock
private HearingAidProfile mHearingAidProfile; private HearingAidProfile mHearingAidProfile;
@Mock
private AudioSwitchPreferenceController.AudioSwitchCallback mAudioSwitchPreferenceCallback;
private Context mContext; private Context mContext;
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
@@ -157,6 +159,7 @@ public class MediaOutputPreferenceControllerTest {
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mScreen.addPreference(mPreference); mScreen.addPreference(mPreference);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.setCallback(mAudioSwitchPreferenceCallback);
} }
@After @After

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.widget;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.support.v7.preference.ListPreference;
import android.widget.ArrayAdapter;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import static org.mockito.Mockito.spy;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = ShadowBluetoothUtils.class)
public class UpdatableListPreferenceDialogFragmentTest {
private Context mContext;
private UpdatableListPreferenceDialogFragment mUpdatableListPrefDlgFragment;
private static final String KEY = "Test_Key";
private ArrayAdapter mAdapter;
private ArrayList<CharSequence> mEntries;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mUpdatableListPrefDlgFragment = UpdatableListPreferenceDialogFragment
.newInstance(KEY, MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES);
mEntries = spy(new ArrayList<>());
mUpdatableListPrefDlgFragment.setEntries(mEntries);
mUpdatableListPrefDlgFragment.
setMetricsCategory(mUpdatableListPrefDlgFragment.getArguments());
initAdapter();
}
private void initAdapter() {
mAdapter = spy(new ArrayAdapter<>(
mContext,
com.android.internal.R.layout.select_dialog_singlechoice,
mEntries));
mUpdatableListPrefDlgFragment.setAdapter(mAdapter);
}
@Test
public void getMetricsCategory() {
assertThat(mUpdatableListPrefDlgFragment.getMetricsCategory())
.isEqualTo(MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES);
}
@Test
public void onListPreferenceUpdated_verifyAdapterCanBeUpdate() {
assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).
isEqualTo(0);
ListPreference listPreference = new ListPreference(mContext);
final CharSequence[] charSequences = {"Test_DEVICE_1", "Test_DEVICE_2"};
listPreference.setEntries(charSequences);
mUpdatableListPrefDlgFragment.onListPreferenceUpdated(listPreference);
assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).
isEqualTo(2);
}
}