From 5b7fc8f3a58ba5fa00d4443628fae16fb3b2f389 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 17 Sep 2018 13:25:36 -0700 Subject: [PATCH] Fix null pointer crash in BT renaming dialog The dialog may become null after onDestroy has been invoked, so we need to catch this case. This CL also moves the listener outside to make it easy to test. Change-Id: I4ce640c5bdaf1f201f9fecb14b3e5e38e10d4b79 Fixes: 115679393 Test: RunSettingsRoboTests --- .../BluetoothNameDialogFragment.java | 41 ++++++---- .../BluetoothNameDialogFragmentTest.java | 82 +++++++++++++++++++ 2 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothNameDialogFragmentTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java index 260b4a81f49..74c39b6f8c2 100644 --- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java @@ -34,6 +34,7 @@ import android.widget.Button; import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import com.android.settings.R; @@ -43,8 +44,14 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment; * Dialog fragment for renaming a Bluetooth device. */ abstract class BluetoothNameDialogFragment extends InstrumentedDialogFragment - implements TextWatcher { - private AlertDialog mAlertDialog; + implements TextWatcher, TextView.OnEditorActionListener { + + // Key to save the edited name and edit status for restoring after rotation + private static final String KEY_NAME = "device_name"; + private static final String KEY_NAME_EDITED = "device_name_edited"; + + @VisibleForTesting + AlertDialog mAlertDialog; private Button mOkButton; EditText mDeviceNameView; @@ -55,10 +62,6 @@ abstract class BluetoothNameDialogFragment extends InstrumentedDialogFragment // This flag is set when the user edits the name (preserved on rotation) private boolean mDeviceNameEdited; - // Key to save the edited name and edit status for restoring after rotation - private static final String KEY_NAME = "device_name"; - private static final String KEY_NAME_EDITED = "device_name_edited"; - /** * @return the title to use for the dialog. */ @@ -123,21 +126,23 @@ abstract class BluetoothNameDialogFragment extends InstrumentedDialogFragment } mDeviceNameView.addTextChangedListener(this); com.android.settings.Utils.setEditTextCursorPosition(mDeviceNameView); - mDeviceNameView.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - setDeviceName(v.getText().toString()); - mAlertDialog.dismiss(); - return true; // action handled - } else { - return false; // not handled - } - } - }); + mDeviceNameView.setOnEditorActionListener(this); return view; } + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + setDeviceName(v.getText().toString()); + if (mAlertDialog != null && mAlertDialog.isShowing()) { + mAlertDialog.dismiss(); + } + return true; // action handled + } else { + return false; // not handled + } + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothNameDialogFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothNameDialogFragmentTest.java new file mode 100644 index 00000000000..350ec9e3e6b --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothNameDialogFragmentTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import android.view.inputmethod.EditorInfo; +import android.widget.TextView; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class BluetoothNameDialogFragmentTest { + + private TestBluetoothNameDialogFragment mBluetoothNameDialogFragment; + private TextView mTextView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mBluetoothNameDialogFragment = new TestBluetoothNameDialogFragment(); + mTextView = new TextView(RuntimeEnvironment.application); + } + + @Test + public void onEditorAction_dialogNull_shouldNotCrash() { + mBluetoothNameDialogFragment.mAlertDialog = null; + + // Should not crash + assertThat( + mBluetoothNameDialogFragment.onEditorAction(mTextView, EditorInfo.IME_ACTION_DONE, + null)).isTrue(); + } + + + /** + * Test fragment for {@link BluetoothNameDialogFragment} to test common methods + */ + public static class TestBluetoothNameDialogFragment extends BluetoothNameDialogFragment { + + @Override + protected int getDialogTitle() { + return 0; + } + + @Override + protected String getDeviceName() { + return null; + } + + @Override + protected void setDeviceName(String deviceName) { + + } + + @Override + public int getMetricsCategory() { + return 0; + } + } + +}