Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d7d9272 authored by Jason Monk's avatar Jason Monk
Browse files

Change DeviceProfilesSettings to DialogFragment

This makes the lifecycle simpler to fix a crash after rotation.
Also do some adjusting of alignment while here.

Bug: 21444336
Bug: 21205689
Change-Id: I67eccf4833f53b5e5088ae5e6038d041e8653565
parent 1b7bff11
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingStart="25dp"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/bluetooth_preference_paired_dialog_name_label"
        android:textAppearance="@android:style/TextAppearance.Material.Body1"
        android:textColor="?android:attr/textColorSecondary"
        android:textDirection="locale"
        android:paddingTop="16dp"
        style="@style/bt_item_label" />

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textNoSuggestions"
        android:maxLength="@integer/bluetooth_name_length"
        android:singleLine="true"
        android:paddingBottom="@dimen/bluetooth_dialog_padding"
        style="@style/bt_item_edit_content" />

    <TextView
        android:id="@+id/profiles_label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:paddingBottom="4dp"
        android:text="@string/bluetooth_device_advanced_profile_header_title"
        android:textAppearance="@android:style/TextAppearance.Material.Body1"
        android:textColor="?android:attr/textColorSecondary" />

    <LinearLayout
        android:id="@+id/profiles_section"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
+9 −78
Original line number Diff line number Diff line
@@ -18,13 +18,10 @@ package com.android.settings.bluetooth;

import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;

import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
@@ -37,14 +34,10 @@ import android.text.Spannable;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;

import com.android.internal.logging.MetricsLogger;
@@ -423,6 +416,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem
        }
    }

    @Override
    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
        setDeviceListGroup(getPreferenceScreen());
        removeAllDevices();
@@ -430,6 +424,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem
    }

    private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // User clicked on advanced options icon for a device in the list
            if (!(v.getTag() instanceof CachedBluetoothDevice)) {
@@ -438,77 +433,13 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem
            }

            final CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
            final Activity activity = getActivity();
            DeviceProfilesSettings profileFragment = (DeviceProfilesSettings)activity.
                getFragmentManager().findFragmentById(R.id.bluetooth_fragment_settings);

            if (mSettingsDialogView != null){
                ViewGroup parent = (ViewGroup) mSettingsDialogView.getParent();
                if (parent != null) {
                    parent.removeView(mSettingsDialogView);
                }
            }

            if (profileFragment == null) {
                LayoutInflater inflater = getActivity().getLayoutInflater();
                mSettingsDialogView = inflater.inflate(R.layout.bluetooth_device_settings, null);
                profileFragment = (DeviceProfilesSettings)activity.getFragmentManager()
                    .findFragmentById(R.id.bluetooth_fragment_settings);

                // To enable scrolling we store the name field in a seperate header and add to
                // the ListView of the profileFragment.
                View header = inflater.inflate(R.layout.bluetooth_device_settings_header, null);
                profileFragment.getListView().addHeaderView(header);
            }

            final View dialogLayout = mSettingsDialogView;
            AlertDialog.Builder settingsDialog = new AlertDialog.Builder(activity);
            profileFragment.setDevice(device);
            final EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
            deviceName.setText(device.getName(), TextView.BufferType.EDITABLE);

            final DeviceProfilesSettings dpsFragment = profileFragment;
            final Context context = v.getContext();
            settingsDialog.setView(dialogLayout);
            settingsDialog.setTitle(R.string.bluetooth_preference_paired_devices);
            settingsDialog.setPositiveButton(R.string.okay,
                    new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
                    device.setName(deviceName.getText().toString());
                }
            });

            settingsDialog.setNegativeButton(R.string.forget,
                    new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    device.unpair();
                    com.android.settings.bluetooth.Utils.updateSearchIndex(activity,
                            BluetoothSettings.class.getName(), device.getName(),
                            context.getResources().getString(R.string.bluetooth_settings),
                            R.drawable.ic_settings_bluetooth, false);
                }
            });

            // We must ensure that the fragment gets destroyed to avoid duplicate fragments.
            settingsDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                public void onDismiss(final DialogInterface dialog) {
                    if (!activity.isDestroyed()) {
                        activity.getFragmentManager().beginTransaction().remove(dpsFragment)
                            .commitAllowingStateLoss();
                    }
                }
            });

            AlertDialog dialog = settingsDialog.create();
            dialog.create();
            dialog.show();

            // We must ensure that clicking on the EditText will bring up the keyboard.
            dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
            Bundle args = new Bundle();
            args.putString(DeviceProfilesSettings.ARG_DEVICE_ADDRESS,
                    device.getDevice().getAddress());
            DeviceProfilesSettings profileSettings = new DeviceProfilesSettings();
            profileSettings.setArguments(args);
            profileSettings.show(getFragmentManager(),
                    DeviceProfilesSettings.class.getSimpleName());
        }
    };

+102 −81
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.settings.bluetooth;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -24,15 +26,18 @@ import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import com.android.internal.logging.MetricsLogger;
import android.widget.TextView;

import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -44,15 +49,12 @@ import com.android.settingslib.bluetooth.PbapServerProfile;

import java.util.HashMap;

/**
 * This preference fragment presents the user with all of the profiles
 * for a particular device, and allows them to be individually connected
 * (or disconnected).
 */
public final class DeviceProfilesSettings extends SettingsPreferenceFragment
        implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
public final class DeviceProfilesSettings extends DialogFragment implements
        CachedBluetoothDevice.Callback, DialogInterface.OnClickListener, OnClickListener {
    private static final String TAG = "DeviceProfilesSettings";

    public static final String ARG_DEVICE_ADDRESS = "device_address";

    private static final String KEY_PROFILE_CONTAINER = "profile_container";
    private static final String KEY_UNPAIR = "unpair";
    private static final String KEY_PBAP_SERVER = "PBAP Server";
@@ -61,7 +63,8 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
    private LocalBluetoothManager mManager;
    private LocalBluetoothProfileManager mProfileManager;

    private PreferenceGroup mProfileContainer;
    private ViewGroup mProfileContainer;
    private TextView mProfileLabel;
    private EditTextPreference mDeviceNamePref;

    private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs
@@ -70,26 +73,59 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
    private AlertDialog mDisconnectDialog;
    private boolean mProfileGroupIsRemoved;

    @Override
    protected int getMetricsCategory() {
        return MetricsLogger.BLUETOOTH_DEVICE_PROFILES;
    }
    private View mRootView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.bluetooth_device_advanced);
        getPreferenceScreen().setOrderingAsAdded(false);
        mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
        mProfileContainer.setLayoutResource(R.layout.bluetooth_preference_category);

        mManager = Utils.getLocalBtManager(getActivity());
        CachedBluetoothDeviceManager deviceManager =
                mManager.getCachedDeviceManager();
        CachedBluetoothDeviceManager deviceManager = mManager.getCachedDeviceManager();

        String address = getArguments().getString(ARG_DEVICE_ADDRESS);
        BluetoothDevice remoteDevice = mManager.getBluetoothAdapter().getRemoteDevice(address);

        mCachedDevice = deviceManager.findDevice(remoteDevice);
        if (mCachedDevice == null) {
            mCachedDevice = deviceManager.addDevice(mManager.getBluetoothAdapter(),
                    mManager.getProfileManager(), remoteDevice);
        }
        mProfileManager = mManager.getProfileManager();
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        mRootView = LayoutInflater.from(getContext()).inflate(R.layout.device_profiles_settings,
                null);
        mProfileContainer = (ViewGroup) mRootView.findViewById(R.id.profiles_section);
        mProfileLabel = (TextView) mRootView.findViewById(R.id.profiles_label);
        final EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
        deviceName.setText(mCachedDevice.getName(), TextView.BufferType.EDITABLE);
        return new AlertDialog.Builder(getContext())
                .setView(mRootView)
                .setNegativeButton(R.string.forget, this)
                .setPositiveButton(R.string.okay, this)
                .setTitle(R.string.bluetooth_preference_paired_devices)
                .create();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
            case DialogInterface.BUTTON_POSITIVE:
                EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
                mCachedDevice.setName(deviceName.getText().toString());
                break;
            case DialogInterface.BUTTON_NEGATIVE:
                mCachedDevice.unpair();
                com.android.settings.bluetooth.Utils.updateSearchIndex(getContext(),
                        BluetoothSettings.class.getName(), mCachedDevice.getName(),
                        getString(R.string.bluetooth_settings),
                        R.drawable.ic_settings_bluetooth, false);
                break;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
@@ -115,7 +151,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
        if (mCachedDevice != null) {
            mCachedDevice.registerCallback(this);
            if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
                finish();
                dismiss();
                return;
            }
            refresh();
@@ -144,37 +180,39 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
    }

    private void addPreferencesForProfiles() {
        mProfileContainer.removeAll();
        mProfileContainer.removeAllViews();
        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
            Preference pref = createProfilePreference(profile);
            mProfileContainer.addPreference(pref);
            CheckBox pref = createProfilePreference(profile);
            mProfileContainer.addView(pref);
        }

        final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
        // Only provide PBAP cabability if the client device has requested PBAP.
        if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
            final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
            CheckBoxPreference pbapPref = createProfilePreference(psp);
            mProfileContainer.addPreference(pbapPref);
            CheckBox pbapPref = createProfilePreference(psp);
            mProfileContainer.addView(pbapPref);
        }

        final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
        final int mapPermission = mCachedDevice.getMessagePermissionChoice();
        if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
            CheckBoxPreference mapPreference = createProfilePreference(mapProfile);
            mProfileContainer.addPreference(mapPreference);
            CheckBox mapPreference = createProfilePreference(mapProfile);
            mProfileContainer.addView(mapPreference);
        }

        showOrHideProfileGroup();
    }

    private void showOrHideProfileGroup() {
        int numProfiles = mProfileContainer.getPreferenceCount();
        int numProfiles = mProfileContainer.getChildCount();
        if (!mProfileGroupIsRemoved && numProfiles == 0) {
            getPreferenceScreen().removePreference(mProfileContainer);
            mProfileContainer.setVisibility(View.GONE);
            mProfileLabel.setVisibility(View.GONE);
            mProfileGroupIsRemoved = true;
        } else if (mProfileGroupIsRemoved && numProfiles != 0) {
            getPreferenceScreen().addPreference(mProfileContainer);
            mProfileContainer.setVisibility(View.VISIBLE);
            mProfileLabel.setVisibility(View.VISIBLE);
            mProfileGroupIsRemoved = false;
        }
    }
@@ -187,43 +225,29 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
     * @return A preference that allows the user to choose whether this profile
     *         will be connected to.
     */
    private CheckBoxPreference createProfilePreference(LocalBluetoothProfile profile) {
        CheckBoxPreference pref = new CheckBoxPreference(getActivity());
        pref.setLayoutResource(R.layout.preference_start_widget);
        pref.setKey(profile.toString());
        pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
        pref.setPersistent(false);
        pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal()));
        pref.setOnPreferenceChangeListener(this);

        int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
        if (iconResource != 0) {
            pref.setIcon(getActivity().getDrawable(iconResource));
        }
    private CheckBox createProfilePreference(LocalBluetoothProfile profile) {
        CheckBox pref = new CheckBox(getActivity());
        pref.setTag(profile.toString());
        pref.setText(profile.getNameResource(mCachedDevice.getDevice()));
        pref.setOnClickListener(this);

        refreshProfilePreference(pref, profile);

        return pref;
    }

    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (preference == mDeviceNamePref) {
            mCachedDevice.setName((String) newValue);
        } else if (preference instanceof CheckBoxPreference) {
            LocalBluetoothProfile prof = getProfileOf(preference);
            onProfileClicked(prof, (CheckBoxPreference) preference);
            return false;   // checkbox will update from onDeviceAttributesChanged() callback
        } else {
            return false;
    @Override
    public void onClick(View v) {
        if (v instanceof CheckBox) {
            LocalBluetoothProfile prof = getProfileOf(v);
            onProfileClicked(prof, (CheckBox) v);
        }

        return true;
    }

    private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
    private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) {
        BluetoothDevice device = mCachedDevice.getDevice();

        if (profilePref.getKey().equals(KEY_PBAP_SERVER)) {
        if (KEY_PBAP_SERVER.equals(profilePref.getTag())) {
            final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
                == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
                : CachedBluetoothDevice.ACCESS_ALLOWED;
@@ -232,11 +256,9 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
            return;
        }

        int status = profile.getConnectionStatus(device);
        boolean isConnected =
                status == BluetoothProfile.STATE_CONNECTED;

        if (profilePref.isChecked()) {
        if (!profilePref.isChecked()) {
            // Recheck it, until the dialog is done.
            profilePref.setChecked(true);
            askDisconnect(mManager.getForegroundActivity(), profile);
        } else {
            if (profile instanceof MapProfile) {
@@ -281,8 +303,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
                if (profile instanceof MapProfile) {
                    device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
                }
                refreshProfilePreference(
                        (CheckBoxPreference)findPreference(profile.toString()), profile);
                refreshProfilePreference(findProfile(profile.toString()), profile);
            }
        };

@@ -296,7 +317,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
    }

    private void refresh() {
        final EditText deviceNameField = (EditText) getView().findViewById(R.id.name);
        final EditText deviceNameField = (EditText) mRootView.findViewById(R.id.name);
        if (deviceNameField != null) {
            deviceNameField.setText(mCachedDevice.getName());
        }
@@ -306,26 +327,30 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment

    private void refreshProfiles() {
        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
            CheckBoxPreference profilePref = (CheckBoxPreference)findPreference(profile.toString());
            CheckBox profilePref = findProfile(profile.toString());
            if (profilePref == null) {
                profilePref = createProfilePreference(profile);
                mProfileContainer.addPreference(profilePref);
                mProfileContainer.addView(profilePref);
            } else {
                refreshProfilePreference(profilePref, profile);
            }
        }
        for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
            Preference profilePref = findPreference(profile.toString());
            CheckBox profilePref = findProfile(profile.toString());
            if (profilePref != null) {
                Log.d(TAG, "Removing " + profile.toString() + " from profile list");
                mProfileContainer.removePreference(profilePref);
                mProfileContainer.removeView(profilePref);
            }
        }

        showOrHideProfileGroup();
    }

    private void refreshProfilePreference(CheckBoxPreference profilePref,
    private CheckBox findProfile(String profile) {
        return (CheckBox) mProfileContainer.findViewWithTag(profile);
    }

    private void refreshProfilePreference(CheckBox profilePref,
            LocalBluetoothProfile profile) {
        BluetoothDevice device = mCachedDevice.getDevice();

@@ -349,21 +374,17 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
        }
    }

    private LocalBluetoothProfile getProfileOf(Preference pref) {
        if (!(pref instanceof CheckBoxPreference)) {
    private LocalBluetoothProfile getProfileOf(View v) {
        if (!(v instanceof CheckBox)) {
            return null;
        }
        String key = pref.getKey();
        String key = (String) v.getTag();
        if (TextUtils.isEmpty(key)) return null;

        try {
            return mProfileManager.getProfileByName(pref.getKey());
            return mProfileManager.getProfileByName(key);
        } catch (IllegalArgumentException ignored) {
            return null;
        }
    }

    private int getProfilePreferenceIndex(int profIndex) {
        return mProfileContainer.getOrder() + profIndex * 10;
    }
}