fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2021 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.car.qc;
import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
import static com.android.car.qc.QCItem.QC_TYPE_ACTION_TOGGLE;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
private static final String TEST_CONTENT_DESCRIPTION = "test_content_description";
@Test
public void onCreate_invalidType_throwsException() {
assertThrows(IllegalArgumentException.class,
() -> createAction("INVALID_TYPE", /* action= */ null,
/* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/
null));
}
@Test
public void onCreateSwitch_hasCorrectType() {
QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, /* action= */ null,
/* disabledAction= */ null, /* icon= */null, /* contentDescription=*/ null);
assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
}
@Test
public void onCreateToggle_hasCorrectType() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
/* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/ null);
assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
}
@Test
public void onBundle_nullActions_noCrash() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
/* disabledAction= */ null, mDefaultIcon, /* contentDescription=*/ null);
writeAndLoadFromBundle(action);
// Test passes if this doesn't crash
}
@Test
public void onBundle_nullIcon_noCrash() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
mDefaultDisabledAction, /* icon= */ null, /* contentDescription=*/ null);
writeAndLoadFromBundle(action);
// Test passes if this doesn't crash
}
@Test
public void onBundle_switch_accurateData() {
QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, mDefaultAction,
mDefaultDisabledAction, /* icon= */ null, TEST_CONTENT_DESCRIPTION);
QCActionItem newAction = writeAndLoadFromBundle(action);
assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
assertThat(newAction.isChecked()).isTrue();
assertThat(newAction.isEnabled()).isTrue();
assertThat(newAction.isClickableWhileDisabled()).isFalse();
assertThat(newAction.getPrimaryAction()).isNotNull();
assertThat(newAction.getIcon()).isNull();
assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
}
@Test
public void onBundle_toggle_accurateDate() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
mDefaultDisabledAction, mDefaultIcon, TEST_CONTENT_DESCRIPTION);
QCActionItem newAction = writeAndLoadFromBundle(action);
assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
assertThat(newAction.isChecked()).isTrue();
assertThat(newAction.isEnabled()).isTrue();
assertThat(newAction.isClickableWhileDisabled()).isFalse();
assertThat(newAction.getPrimaryAction()).isNotNull();
assertThat(newAction.getIcon()).isNotNull();
assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
}
private QCActionItem createAction(String type, PendingIntent action,
PendingIntent disabledAction, Icon icon, String contentDescription) {
return new QCActionItem.Builder(type)
.setChecked(true)
.setEnabled(true)
.setAction(action)
.setDisabledClickAction(disabledAction)
.setIcon(icon)
.setContentDescription(contentDescription)
.build();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 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.car.qc;
import android.R;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import androidx.test.core.app.ApplicationProvider;
public abstract class QCItemTestCase<T extends QCItem> {
protected static final String BUNDLE_KEY = "BUNDLE_KEY";
protected static final String TEST_TITLE = "TEST TITLE";
protected static final String TEST_SUBTITLE = "TEST SUBTITLE";
protected final Context mContext = ApplicationProvider.getApplicationContext();
protected PendingIntent mDefaultAction = PendingIntent.getActivity(mContext,
/* requestCode= */ 0, new Intent(), PendingIntent.FLAG_IMMUTABLE);
protected PendingIntent mDefaultDisabledAction = PendingIntent.getActivity(mContext,
/* requestCode= */ 1, new Intent(), PendingIntent.FLAG_IMMUTABLE);
protected Icon mDefaultIcon = Icon.createWithResource(mContext, R.drawable.btn_star);
protected T writeAndLoadFromBundle(T item) {
Bundle bundle = new Bundle();
bundle.putParcelable(BUNDLE_KEY, item);
return bundle.getParcelable(BUNDLE_KEY);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 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.car.qc;
import static com.android.car.qc.QCItem.QC_TYPE_LIST;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class QCListTest extends QCItemTestCase<QCList> {
@Test
public void onCreate_hasCorrectType() {
QCList list = createList(Collections.emptyList());
assertThat(list.getType()).isEqualTo(QC_TYPE_LIST);
}
@Test
public void createFromParcel_accurateData() {
QCRow row = new QCRow.Builder()
.setTitle(TEST_TITLE)
.setSubtitle(TEST_SUBTITLE)
.setIcon(mDefaultIcon)
.setPrimaryAction(mDefaultAction)
.build();
QCList list = createList(Collections.singletonList(row));
QCList newList = writeAndLoadFromBundle(list);
assertThat(newList.getType()).isEqualTo(QC_TYPE_LIST);
assertThat(newList.getRows().size()).isEqualTo(1);
}
private QCList createList(List<QCRow> rows) {
QCList.Builder builder = new QCList.Builder();
for (QCRow row : rows) {
builder.addRow(row);
}
return builder.build();
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2021 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.car.qc;
import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
import static com.android.car.qc.QCItem.QC_TYPE_ROW;
import static com.google.common.truth.Truth.assertThat;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class QCRowTest extends QCItemTestCase<QCRow> {
@Test
public void onCreate_hasCorrectType() {
QCRow row = createRow(/* action= */ null, /* disabledAction= */ null, /* icon= */ null);
assertThat(row.getType()).isEqualTo(QC_TYPE_ROW);
}
@Test
public void onBundle_nullActions_noCrash() {
QCRow row = createRow(/* action= */ null, /* disabledAction= */ null, mDefaultIcon);
writeAndLoadFromBundle(row);
// Test passes if this doesn't crash
}
@Test
public void onBundle_nullIcon_noCrash() {
QCRow row = createRow(mDefaultAction, mDefaultDisabledAction, /* icon= */ null);
writeAndLoadFromBundle(row);
// Test passes if this doesn't crash
}
@Test
public void createFromParcel_accurateData() {
QCRow row = createRow(mDefaultAction, mDefaultDisabledAction, mDefaultIcon);
QCRow newRow = writeAndLoadFromBundle(row);
assertThat(newRow.getType()).isEqualTo(QC_TYPE_ROW);
assertThat(newRow.getTitle()).isEqualTo(TEST_TITLE);
assertThat(newRow.getSubtitle()).isEqualTo(TEST_SUBTITLE);
assertThat(newRow.getPrimaryAction()).isNotNull();
assertThat(newRow.getStartIcon()).isNotNull();
}
@Test
public void createFromParcel_accurateData_startItem() {
QCActionItem item = new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build();
QCRow row = createRow(/* action= */ null, /* disabledAction= */ null, /* icon= */ null,
Collections.singletonList(item), Collections.emptyList(), Collections.emptyList());
QCRow newRow = writeAndLoadFromBundle(row);
assertThat(newRow.getStartItems().size()).isEqualTo(1);
}
@Test
public void createFromParcel_accurateData_endItem() {
QCActionItem item = new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build();
QCRow row = createRow(/* action= */ null, /* disabledAction= */ null, /* icon= */ null,
Collections.emptyList(), Collections.singletonList(item), Collections.emptyList());
QCRow newRow = writeAndLoadFromBundle(row);
assertThat(newRow.getEndItems().size()).isEqualTo(1);
}
@Test
public void createFromParcel_accurateData_slider() {
QCSlider slider = new QCSlider.Builder().build();
QCRow row = createRow(/* action= */ null, /* disabledAction= */ null, /* icon= */ null,
Collections.emptyList(), Collections.emptyList(),
Collections.singletonList(slider));
QCRow newRow = writeAndLoadFromBundle(row);
assertThat(newRow.getSlider()).isNotNull();
}
private QCRow createRow(PendingIntent action, PendingIntent disabledAction, Icon icon) {
return createRow(action, disabledAction, icon, Collections.emptyList(),
Collections.emptyList(), Collections.emptyList());
}
private QCRow createRow(PendingIntent action, PendingIntent disabledAction, Icon icon,
List<QCActionItem> startItems, List<QCActionItem> endItems, List<QCSlider> sliders) {
QCRow.Builder builder = new QCRow.Builder()
.setTitle(TEST_TITLE)
.setSubtitle(TEST_SUBTITLE)
.setIcon(icon)
.setPrimaryAction(action)
.setDisabledClickAction(disabledAction);
for (QCActionItem item : startItems) {
builder.addStartItem(item);
}
for (QCActionItem item : endItems) {
builder.addEndItem(item);
}
for (QCSlider slider : sliders) {
builder.addSlider(slider);
}
return builder.build();
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2021 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.car.qc;
import static com.android.car.qc.QCItem.QC_TYPE_SLIDER;
import static com.google.common.truth.Truth.assertThat;
import android.app.PendingIntent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCSliderTest extends QCItemTestCase<QCSlider> {
private static final int MIN = 50;
private static final int MAX = 150;
private static final int VALUE = 75;
@Test
public void onCreate_hasCorrectType() {
QCSlider slider = createSlider(/* action= */ null, /* disabledAction= */ null);
assertThat(slider.getType()).isEqualTo(QC_TYPE_SLIDER);
}
@Test
public void onBundle_nullActions_noCrash() {
QCSlider slider = createSlider(/* action= */ null, /* disabledAction= */ null);
writeAndLoadFromBundle(slider);
// Test passes if this doesn't crash
}
@Test
public void createFromParcel_accurateData() {
QCSlider slider = createSlider(mDefaultAction, mDefaultDisabledAction);
QCSlider newSlider = writeAndLoadFromBundle(slider);
assertThat(newSlider.getType()).isEqualTo(QC_TYPE_SLIDER);
assertThat(newSlider.getPrimaryAction()).isNotNull();
assertThat(newSlider.getDisabledClickAction()).isNotNull();
assertThat(newSlider.getMin()).isEqualTo(MIN);
assertThat(newSlider.getMax()).isEqualTo(MAX);
assertThat(newSlider.getValue()).isEqualTo(VALUE);
}
private QCSlider createSlider(PendingIntent action, PendingIntent disabledAction) {
return new QCSlider.Builder()
.setMin(MIN)
.setMax(MAX)
.setValue(VALUE)
.setInputAction(action)
.setDisabledClickAction(disabledAction)
.build();
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2021 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.car.qc;
import static com.android.car.qc.QCItem.QC_TYPE_TILE;
import static com.google.common.truth.Truth.assertThat;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCTileTest extends QCItemTestCase<QCTile> {
@Test
public void onCreate_hasCorrectType() {
QCTile tile = createTile(/* action= */ null, /* disabledAction= */ null, /* icon= */ null);
assertThat(tile.getType()).isEqualTo(QC_TYPE_TILE);
}
@Test
public void onBundle_nullAction_noCrash() {
QCTile tile = createTile(/* action= */ null, /* disabledAction= */ null, mDefaultIcon);
writeAndLoadFromBundle(tile);
// Test passes if this doesn't crash
}
@Test
public void onBundle_nullIcon_noCrash() {
QCTile tile = createTile(mDefaultAction, mDefaultDisabledAction, /* icon= */ null);
writeAndLoadFromBundle(tile);
// Test passes if this doesn't crash
}
@Test
public void createFromParcel_accurateData() {
QCTile tile = createTile(mDefaultAction, mDefaultDisabledAction, mDefaultIcon);
QCTile newTile = writeAndLoadFromBundle(tile);
assertThat(newTile.getType()).isEqualTo(QC_TYPE_TILE);
assertThat(newTile.getSubtitle()).isEqualTo(TEST_SUBTITLE);
assertThat(newTile.isChecked()).isTrue();
assertThat(newTile.isEnabled()).isTrue();
assertThat(newTile.getPrimaryAction()).isNotNull();
assertThat(newTile.getDisabledClickAction()).isNotNull();
assertThat(newTile.getIcon()).isNotNull();
}
private QCTile createTile(PendingIntent action, PendingIntent disabledAction, Icon icon) {
return new QCTile.Builder()
.setSubtitle(TEST_SUBTITLE)
.setChecked(true)
.setEnabled(true)
.setAction(action)
.setDisabledClickAction(disabledAction)
.setIcon(icon)
.build();
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2021 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.car.qc.controller;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import androidx.lifecycle.Observer;
import androidx.test.core.app.ApplicationProvider;
import com.android.car.qc.QCItem;
import com.android.car.qc.QCTile;
import org.junit.Test;
public abstract class BaseQCControllerTestCase<T extends BaseQCController> {
protected final Context mContext = spy(ApplicationProvider.getApplicationContext());
protected abstract T getController();
@Test
public void listen_updateListeningCalled() {
T spiedController = spy(getController());
spiedController.listen(true);
verify(spiedController).updateListening();
}
@Test
public void addObserver_updateListeningCalled() {
Observer<QCItem> observer = mock(Observer.class);
T spiedController = spy(getController());
spiedController.addObserver(observer);
verify(spiedController).updateListening();
}
@Test
public void removeObserver_updateListeningCalled() {
Observer<QCItem> observer = mock(Observer.class);
T spiedController = spy(getController());
spiedController.removeObserver(observer);
verify(spiedController).updateListening();
}
@Test
public void onQCItemUpdated_observersNotified() {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().onQCItemUpdated(new QCTile.Builder().build());
verify(observer).onChanged(any(QCItem.class));
}
@Test
public void onDestroy_cleanUpController() {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
getController().destroy();
assertThat(getController().mObservers.size()).isEqualTo(0);
assertThat(getController().mShouldListen).isFalse();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 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.car.qc.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import androidx.lifecycle.Observer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCItem;
import com.android.car.qc.provider.BaseLocalQCProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@RunWith(AndroidJUnit4.class)
public class LocalQCControllerTest extends BaseQCControllerTestCase<LocalQCController> {
private LocalQCController mController;
private BaseLocalQCProvider mProvider;
@Override
protected LocalQCController getController() {
if (mController == null) {
mProvider = mock(BaseLocalQCProvider.class);
mController = new LocalQCController(mContext, mProvider);
}
return mController;
}
@Test
public void onCreate_setsProviderNotifier() {
getController(); // instantiate
verify(mProvider).setNotifier(any());
}
@Test
public void onBind_updatesQCItem() {
Observer<QCItem> observer = mock(Observer.class);
LocalQCController spiedController = spy(getController());
spiedController.addObserver(observer);
Mockito.reset(mProvider);
spiedController.bind();
verify(mProvider).getQCItem();
verify(spiedController).onQCItemUpdated(any());
}
@Test
public void updateListening_updatesProviderListening() {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
verify(mProvider).shouldListen(true);
getController().listen(false);
verify(mProvider).shouldListen(false);
}
@Test
public void updateListening_listen_updatesQCItem() {
Observer<QCItem> observer = mock(Observer.class);
LocalQCController spiedController = spy(getController());
spiedController.addObserver(observer);
Mockito.reset(mProvider);
spiedController.listen(true);
verify(mProvider).getQCItem();
verify(spiedController).onQCItemUpdated(any());
}
@Test
public void onDestroy_callsProviderDestroy() {
getController().destroy();
verify(mProvider).onDestroy();
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2021 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.car.qc.controller;
import static com.android.car.qc.provider.BaseQCProvider.EXTRA_URI;
import static com.android.car.qc.testutils.TestQCProvider.IS_DESTROYED_KEY;
import static com.android.car.qc.testutils.TestQCProvider.IS_SUBSCRIBED_KEY;
import static com.android.car.qc.testutils.TestQCProvider.KEY_DEFAULT;
import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_DESTROYED;
import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_SUBSCRIBED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.lifecycle.Observer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.car.qc.QCItem;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class RemoteQCControllerTest extends BaseQCControllerTestCase<RemoteQCController> {
private final Uri mDefaultUri = Uri.parse(
"content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_DEFAULT);
private RemoteQCController mController;
@Override
protected RemoteQCController getController() {
if (mController == null) {
mController = new RemoteQCController(mContext, mDefaultUri, mContext.getMainExecutor());
}
return mController;
}
@Test
public void onBind_updatesQCItem() {
Observer<QCItem> observer = mock(Observer.class);
RemoteQCController spiedController = spy(getController());
spiedController.addObserver(observer);
spiedController.bind();
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(spiedController).onQCItemUpdated(notNull());
}
@Test
public void updateListening_listen_updatesQCItem() {
Observer<QCItem> observer = mock(Observer.class);
RemoteQCController spiedController = spy(getController());
spiedController.addObserver(observer);
spiedController.listen(true);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(spiedController).onQCItemUpdated(notNull());
}
@Test
public void updateListening_listen_providerSubscribed() throws RemoteException {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
Bundle res = getController().getClient().call(METHOD_IS_SUBSCRIBED, null, extras);
assertThat(res).isNotNull();
boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, false);
assertThat(isSubscribed).isTrue();
}
@Test
public void updateListening_doNotListen_providerUnsubscribed() throws RemoteException {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
getController().listen(false);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
Bundle res = getController().getClient().call(METHOD_IS_SUBSCRIBED, null, extras);
assertThat(res).isNotNull();
boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, true);
assertThat(isSubscribed).isFalse();
}
@Test
public void updateListening_listen_registerContentObserver() {
ContentResolver resolver = mock(ContentResolver.class);
when(mContext.getContentResolver()).thenReturn(resolver);
when(resolver.acquireContentProviderClient(mDefaultUri)).thenReturn(
mock(ContentProviderClient.class));
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(resolver).registerContentObserver(eq(mDefaultUri), eq(true),
any(ContentObserver.class));
}
@Test
public void updateListening_doNotListen_unregisterContentObserver() {
ContentResolver resolver = mock(ContentResolver.class);
when(mContext.getContentResolver()).thenReturn(resolver);
when(resolver.acquireContentProviderClient(mDefaultUri)).thenReturn(
mock(ContentProviderClient.class));
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
getController().listen(false);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
verify(resolver).unregisterContentObserver(any(ContentObserver.class));
}
@Test
public void onDestroy_callsProviderOnDestroy() throws RemoteException {
Observer<QCItem> observer = mock(Observer.class);
getController().addObserver(observer);
getController().listen(true);
getController().listen(false);
getController().destroy();
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
Bundle res = getController().getClient().call(METHOD_IS_DESTROYED, null, extras);
assertThat(res).isNotNull();
boolean isDestroyed = res.getBoolean(IS_DESTROYED_KEY, false);
assertThat(isDestroyed).isTrue();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 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.car.qc.provider;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCItem;
import com.android.car.qc.QCTile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class BaseLocalQCProviderTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private TestBaseLocalQCProvider mProvider;
@Before
public void setUp() {
mProvider = new TestBaseLocalQCProvider(mContext);
}
@Test
public void getQCItem_returnsItem() {
QCItem item = mProvider.getQCItem();
assertThat(item).isNotNull();
assertThat(item instanceof QCTile).isTrue();
}
@Test
public void listen_callsOnSubscribed() {
mProvider.shouldListen(true);
assertThat(mProvider.isSubscribed()).isTrue();
}
@Test
public void stopListening_callsOnUnsubscribed() {
mProvider.shouldListen(true);
mProvider.shouldListen(false);
assertThat(mProvider.isSubscribed()).isFalse();
}
@Test
public void notifyChange_updateNotified() {
BaseLocalQCProvider.Notifier notifier = mock(BaseLocalQCProvider.Notifier.class);
mProvider.setNotifier(notifier);
mProvider.notifyChange();
verify(notifier).notifyUpdate();
}
private static class TestBaseLocalQCProvider extends BaseLocalQCProvider {
private boolean mIsSubscribed;
TestBaseLocalQCProvider(Context context) {
super(context);
}
@Override
public QCItem getQCItem() {
return new QCTile.Builder().build();
}
@Override
protected void onSubscribed() {
mIsSubscribed = true;
}
@Override
protected void onUnsubscribed() {
mIsSubscribed = false;
}
boolean isSubscribed() {
return mIsSubscribed;
}
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2021 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.car.qc.provider;
import static com.android.car.qc.provider.BaseQCProvider.EXTRA_ITEM;
import static com.android.car.qc.provider.BaseQCProvider.EXTRA_URI;
import static com.android.car.qc.provider.BaseQCProvider.METHOD_BIND;
import static com.android.car.qc.provider.BaseQCProvider.METHOD_DESTROY;
import static com.android.car.qc.provider.BaseQCProvider.METHOD_SUBSCRIBE;
import static com.android.car.qc.provider.BaseQCProvider.METHOD_UNSUBSCRIBE;
import static com.android.car.qc.testutils.TestQCProvider.IS_DESTROYED_KEY;
import static com.android.car.qc.testutils.TestQCProvider.IS_SUBSCRIBED_KEY;
import static com.android.car.qc.testutils.TestQCProvider.KEY_DEFAULT;
import static com.android.car.qc.testutils.TestQCProvider.KEY_SLOW;
import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_DESTROYED;
import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_SUBSCRIBED;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.content.ContentProviderClient;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCItem;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class BaseQCProviderTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Uri mDefaultUri = Uri.parse(
"content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_DEFAULT);
private final Uri mSlowUri =
Uri.parse("content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_SLOW);
private final Uri mDeniedUri =
Uri.parse("content://com.android.car.qc.testutils.DeniedTestQCProvider");
@Test
public void callOnBind_allowed_returnsItem() throws RemoteException {
ContentProviderClient provider = getClient(mDefaultUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
Bundle res = provider.call(METHOD_BIND, null, extras);
assertThat(res).isNotNull();
res.setClassLoader(QCItem.class.getClassLoader());
Parcelable parcelable = res.getParcelable(EXTRA_ITEM);
assertThat(parcelable).isNotNull();
assertThat(parcelable instanceof QCItem).isTrue();
}
@Test
public void callOnBind_noUri_throwsIllegalArgumentException() throws RemoteException {
ContentProviderClient provider = getClient(mDefaultUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
assertThrows(IllegalArgumentException.class,
() -> provider.call(METHOD_BIND, null, extras));
}
@Test
public void callOnBind_slowOperation_throwsRuntimeException() {
ContentProviderClient provider = getClient(mSlowUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mSlowUri);
assertThrows(RuntimeException.class,
() -> provider.call(METHOD_BIND, null, extras));
}
@Test
public void callOnBind_notAllowed_throwsSecurityException() {
ContentProviderClient provider = getClient(mDeniedUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDeniedUri);
assertThrows(SecurityException.class,
() -> provider.call(METHOD_BIND, null, extras));
}
@Test
public void callOnSubscribed_isSubscribed() throws RemoteException {
ContentProviderClient provider = getClient(mDefaultUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
provider.call(METHOD_SUBSCRIBE, null, extras);
Bundle res = provider.call(METHOD_IS_SUBSCRIBED, null, extras);
assertThat(res).isNotNull();
boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, false);
assertThat(isSubscribed).isTrue();
}
@Test
public void callOnUnsubscribed_isUnsubscribed() throws RemoteException {
ContentProviderClient provider = getClient(mDefaultUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
provider.call(METHOD_SUBSCRIBE, null, extras);
provider.call(METHOD_UNSUBSCRIBE, null, extras);
Bundle res = provider.call(METHOD_IS_SUBSCRIBED, null, extras);
assertThat(res).isNotNull();
boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, true);
assertThat(isSubscribed).isFalse();
}
@Test
public void callDestroy_isDestroyed() throws RemoteException {
ContentProviderClient provider = getClient(mDefaultUri);
assertThat(provider).isNotNull();
Bundle extras = new Bundle();
extras.putParcelable(EXTRA_URI, mDefaultUri);
provider.call(METHOD_SUBSCRIBE, null, extras);
provider.call(METHOD_UNSUBSCRIBE, null, extras);
provider.call(METHOD_DESTROY, null, extras);
Bundle res = provider.call(METHOD_IS_DESTROYED, null, extras);
assertThat(res).isNotNull();
boolean isDestroyed = res.getBoolean(IS_DESTROYED_KEY, false);
assertThat(isDestroyed).isTrue();
}
private ContentProviderClient getClient(Uri uri) {
return mContext.getContentResolver().acquireContentProviderClient(uri);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 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.car.qc.testutils;
import java.util.HashSet;
import java.util.Set;
public class AllowedTestQCProvider extends TestQCProvider {
@Override
protected Set<String> getAllowlistedPackages() {
Set<String> allowlist = new HashSet<>();
allowlist.add("com.android.car.qc.tests.unit");
return allowlist;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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.car.qc.testutils;
import java.util.HashSet;
import java.util.Set;
public class DeniedTestQCProvider extends TestQCProvider {
@Override
protected Set<String> getAllowlistedPackages() {
return new HashSet<>();
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 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.car.qc.testutils;
import android.R;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.android.car.qc.QCItem;
import com.android.car.qc.QCTile;
import com.android.car.qc.provider.BaseQCProvider;
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class TestQCProvider extends BaseQCProvider {
public static final String METHOD_IS_SUBSCRIBED = "METHOD_IS_SUBSCRIBED";
public static final String IS_SUBSCRIBED_KEY = "IS_SUBSCRIBED";
public static final String METHOD_IS_DESTROYED = "METHOD_IS_DESTROYED";
public static final String IS_DESTROYED_KEY = "IS_DESTROYED";
public static final String KEY_DEFAULT = "DEFAULT";
public static final String KEY_SLOW = "SLOW";
private final Set<Uri> mSubscribedUris = new HashSet<>();
private final Set<Uri> mDestroyedUris = new HashSet<>();
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (METHOD_IS_SUBSCRIBED.equals(method)) {
Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_URI));
Bundle bundle = new Bundle();
bundle.putBoolean(IS_SUBSCRIBED_KEY, mSubscribedUris.contains(uri));
return bundle;
}
if (METHOD_IS_DESTROYED.equals(method)) {
Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_URI));
Bundle bundle = new Bundle();
bundle.putBoolean(IS_DESTROYED_KEY, mDestroyedUris.contains(uri));
return bundle;
}
return super.call(method, arg, extras);
}
@Override
protected QCItem onBind(@NonNull Uri uri) {
List<String> pathSegments = uri.getPathSegments();
String key = pathSegments.get(0);
if (KEY_DEFAULT.equals(key)) {
return new QCTile.Builder()
.setIcon(Icon.createWithResource(getContext(), R.drawable.btn_star))
.build();
} else if (KEY_SLOW.equals(key)) {
// perform a slow operation that should trigger the strict thread policy
Drawable d = getContext().getDrawable(R.drawable.btn_star);
Bitmap bitmap = drawableToBitmap(d);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b = baos.toByteArray();
Icon icon = Icon.createWithData(b, 0, b.length);
return new QCTile.Builder()
.setIcon(icon)
.build();
}
return null;
}
@Override
protected void onSubscribed(@NonNull Uri uri) {
mSubscribedUris.add(uri);
}
@Override
protected void onUnsubscribed(@NonNull Uri uri) {
mSubscribedUris.remove(uri);
}
@Override
protected void onDestroy(@NonNull Uri uri) {
mDestroyedUris.add(uri);
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2021 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.car.qc.view;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCList;
import com.android.car.qc.QCRow;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCListViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private QCListView mView;
@Before
public void setUp() {
mView = new QCListView(mContext);
}
@Test
public void onChanged_null_noViews() {
mView.onChanged(null);
assertThat(mView.getChildCount()).isEqualTo(0);
}
@Test
public void onChanged_invalidType_throwsIllegalArgumentException() {
QCRow row = new QCRow.Builder().build();
assertThrows(IllegalArgumentException.class,
() -> mView.onChanged(row));
}
@Test
public void onChanged_createsRows() {
QCList list = new QCList.Builder()
.addRow(new QCRow.Builder().build())
.addRow(new QCRow.Builder().build())
.build();
mView.onChanged(list);
assertThat(mView.getChildCount()).isEqualTo(2);
assertThat(mView.getChildAt(0) instanceof QCRowView).isTrue();
assertThat(mView.getChildAt(1) instanceof QCRowView).isTrue();
}
@Test
public void onChanged_decreasedRowCount_removesExtraRows() {
QCList list = new QCList.Builder()
.addRow(new QCRow.Builder().build())
.addRow(new QCRow.Builder().build())
.build();
mView.onChanged(list);
assertThat(mView.getChildCount()).isEqualTo(2);
list = new QCList.Builder()
.addRow(new QCRow.Builder().build())
.build();
mView.onChanged(list);
assertThat(mView.getChildCount()).isEqualTo(1);
}
@Test
public void setActionListener_setsOnChildView() {
QCList list = new QCList.Builder()
.addRow(new QCRow.Builder().build())
.addRow(new QCRow.Builder().build())
.build();
mView.onChanged(list);
assertThat(mView.getChildCount()).isEqualTo(2);
QCRowView row1 = (QCRowView) mView.getChildAt(0);
QCRowView row2 = (QCRowView) mView.getChildAt(1);
ExtendedMockito.spyOn(row1);
ExtendedMockito.spyOn(row2);
QCView.QCActionListener listener = mock(QCView.QCActionListener.class);
mView.setActionListener(listener);
ExtendedMockito.verify(row1).setActionListener(listener);
ExtendedMockito.verify(row2).setActionListener(listener);
}
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright (C) 2021 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.car.qc.view;
import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
import static com.android.car.qc.QCItem.QC_TYPE_ACTION_TOGGLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCActionItem;
import com.android.car.qc.QCRow;
import com.android.car.qc.QCSlider;
import com.android.car.qc.R;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCRowViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private QCRowView mView;
@Before
public void setUp() {
mView = new QCRowView(mContext);
}
@Test
public void setRow_null_notVisible() {
mView.setRow(null);
assertThat(mView.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void setRow_notNull_visible() {
QCRow row = new QCRow.Builder().build();
mView.setRow(row);
assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setRow_setsTitle() {
String title = "TEST_TITLE";
QCRow row = new QCRow.Builder().setTitle(title).build();
mView.setRow(row);
TextView titleView = mView.findViewById(R.id.qc_title);
assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(titleView.getText().toString()).isEqualTo(title);
}
@Test
public void setRow_setsSubtitle() {
String subtitle = "TEST_TITLE";
QCRow row = new QCRow.Builder().setSubtitle(subtitle).build();
mView.setRow(row);
TextView subtitleView = mView.findViewById(R.id.qc_summary);
assertThat(subtitleView.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(subtitleView.getText().toString()).isEqualTo(subtitle);
}
@Test
public void setRow_setsIcon() {
Icon icon = Icon.createWithResource(mContext, android.R.drawable.btn_star);
QCRow row = new QCRow.Builder().setIcon(icon).build();
mView.setRow(row);
ImageView iconView = mView.findViewById(R.id.qc_icon);
assertThat(iconView.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(iconView.getDrawable()).isNotNull();
}
@Test
@UiThreadTest
public void setRow_createsStartItems() {
QCRow row = new QCRow.Builder()
.addStartItem(new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build())
.addStartItem(new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).build())
.build();
mView.setRow(row);
LinearLayout startContainer = mView.findViewById(R.id.qc_row_start_items);
assertThat(startContainer.getChildCount()).isEqualTo(2);
assertThat((View) startContainer.getChildAt(0).findViewById(
android.R.id.switch_widget)).isNotNull();
assertThat((View) startContainer.getChildAt(1).findViewById(
R.id.qc_toggle_button)).isNotNull();
}
@Test
@UiThreadTest
public void setRow_createsEndItems() {
QCRow row = new QCRow.Builder()
.addEndItem(new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build())
.addEndItem(new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).build())
.build();
mView.setRow(row);
LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
assertThat(endContainer.getChildCount()).isEqualTo(2);
assertThat((View) endContainer.getChildAt(0).findViewById(
android.R.id.switch_widget)).isNotNull();
assertThat((View) endContainer.getChildAt(1).findViewById(
R.id.qc_toggle_button)).isNotNull();
}
@Test
public void setRow_noSlider_sliderViewNotVisible() {
QCRow row = new QCRow.Builder().build();
mView.setRow(row);
LinearLayout sliderContainer = mView.findViewById(R.id.qc_seekbar_wrapper);
assertThat(sliderContainer.getVisibility()).isEqualTo(View.GONE);
}
@Test
@UiThreadTest
public void setRow_hasSlider_sliderViewVisible() {
QCRow row = new QCRow.Builder()
.addSlider(new QCSlider.Builder().build())
.build();
mView.setRow(row);
LinearLayout sliderContainer = mView.findViewById(R.id.qc_seekbar_wrapper);
assertThat(sliderContainer.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void onRowClick_firesAction() throws PendingIntent.CanceledException {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder().setPrimaryAction(action).build();
mView.setRow(row);
mView.findViewById(R.id.qc_row_content).performClick();
verify(action).send(any(Context.class), anyInt(), eq(null));
}
@Test
public void onSwitchClick_firesAction() throws PendingIntent.CanceledException {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder()
.addEndItem(
new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).setAction(action).build())
.build();
mView.setRow(row);
LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
assertThat(endContainer.getChildCount()).isEqualTo(1);
endContainer.getChildAt(0).performClick();
verify(action).send(any(Context.class), anyInt(), any(Intent.class));
}
@Test
@UiThreadTest
public void onToggleClick_firesAction() throws PendingIntent.CanceledException {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder()
.addEndItem(
new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).setAction(action).build())
.build();
mView.setRow(row);
LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
assertThat(endContainer.getChildCount()).isEqualTo(1);
endContainer.getChildAt(0).performClick();
verify(action).send(any(Context.class), anyInt(), any(Intent.class));
}
@Test
@UiThreadTest
public void onSliderChange_firesAction() throws PendingIntent.CanceledException {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder()
.addSlider(new QCSlider.Builder().setInputAction(action).build())
.build();
mView.setRow(row);
SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
seekBar.setProgress(50);
MotionEvent motionEvent = ExtendedMockito.mock(MotionEvent.class);
ExtendedMockito.when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
seekBar.onTouchEvent(motionEvent);
verify(action).send(any(Context.class), anyInt(), any(Intent.class));
}
@Test
@UiThreadTest
public void setRow_switchViewThumbTintList() {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder()
.addEndItem(
new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).setAction(action).build())
.build();
mView.setRow(row);
LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
assertThat(endContainer.getChildCount()).isEqualTo(1);
Switch switchView = (Switch) endContainer.getChildAt(0);
assertThat(switchView.getThumbTintList()).isNotNull();
ColorStateList switchColorStateList = switchView.getThumbTintList();
int[] enabledState = {android.R.attr.state_enabled};
int[] disabledState = {-android.R.attr.state_enabled};
assertThat(switchColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
switchColorStateList.getColorForState(disabledState, 0));
}
@Test
@UiThreadTest
public void setRow_sliderViewThumbTintList() {
PendingIntent action = mock(PendingIntent.class);
QCRow row = new QCRow.Builder()
.addSlider(new QCSlider.Builder().setInputAction(action).build())
.build();
mView.setRow(row);
SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
assertThat(seekBar.getThumbTintList()).isNotNull();
ColorStateList seekBarColorStateList = seekBar.getThumbTintList();
int[] enabledState = {android.R.attr.state_enabled};
int[] disabledState = {-android.R.attr.state_enabled};
assertThat(seekBarColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
seekBarColorStateList.getColorForState(disabledState, 0));
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 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.car.qc.view;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.view.MotionEvent;
import android.widget.SeekBar;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
public class QCSeekBarViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private final QCSeekBarView mView = new QCSeekBarView(mContext);
@Mock
private MotionEvent mMotionEvent;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
}
@Test
public void enabled_standardTouchEvent() {
assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
}
@Test
public void disabled_standardTouchEvent() {
mView.setEnabled(false);
assertThat(mView.onTouchEvent(mMotionEvent)).isFalse();
}
@Test
public void clickableWhileDisabled_customTouchEvent() {
mView.setEnabled(false);
mView.setClickableWhileDisabled(true);
assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
}
@Test
public void clickableWhileDisabled_actionDown_doesNotTriggerDisabledClickListener() {
AtomicBoolean called = new AtomicBoolean(false);
Consumer<SeekBar> disabledClickListener = seekBar -> called.set(true);
mView.setEnabled(false);
mView.setClickableWhileDisabled(true);
mView.setDisabledClickListener(disabledClickListener);
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
assertThat(called.get()).isFalse();
}
@Test
public void clickableWhileDisabled_actionUp_triggersDisabledClickListener() {
AtomicBoolean called = new AtomicBoolean(false);
Consumer<SeekBar> disabledClickListener = seekBar -> called.set(true);
mView.setEnabled(false);
mView.setClickableWhileDisabled(true);
mView.setDisabledClickListener(disabledClickListener);
assertThat(mView.onTouchEvent(mMotionEvent)).isTrue();
assertThat(called.get()).isTrue();
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2021 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.car.qc.view;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertThrows;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.LayerDrawable;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCRow;
import com.android.car.qc.QCTile;
import com.android.car.qc.R;
import com.android.car.ui.uxr.DrawableStateToggleButton;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCTileViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private QCTileView mView;
@Before
public void setUp() {
mView = new QCTileView(mContext);
}
@Test
public void onChanged_null_noViews() {
mView.onChanged(null);
assertThat(mView.getChildCount()).isEqualTo(0);
}
@Test
public void onChanged_invalidType_throwsIllegalArgumentException() {
QCRow row = new QCRow.Builder().build();
assertThrows(IllegalArgumentException.class,
() -> mView.onChanged(row));
}
@Test
@UiThreadTest
public void onChanged_setsSubtitleView() {
String subtitle = "TEST_SUBTITLE";
QCTile tile = new QCTile.Builder().setSubtitle(subtitle).build();
mView.onChanged(tile);
TextView subtitleView = mView.findViewById(android.R.id.summary);
assertThat(subtitleView.getText().toString()).isEqualTo(subtitle);
}
@Test
@UiThreadTest
public void onChanged_setsButtonState() {
QCTile tile = new QCTile.Builder().setChecked(true).setEnabled(true).build();
mView.onChanged(tile);
DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
assertThat(button.isEnabled()).isTrue();
assertThat(button.isChecked()).isTrue();
}
@Test
@UiThreadTest
public void onChanged_setsIcon() {
Icon icon = Icon.createWithResource(mContext, android.R.drawable.btn_star);
QCTile tile = new QCTile.Builder().setIcon(icon).build();
mView.onChanged(tile);
DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
Drawable buttonDrawable = button.getButtonDrawable();
assertThat(buttonDrawable).isNotNull();
assertThat(buttonDrawable instanceof LayerDrawable).isTrue();
assertThat(((LayerDrawable) buttonDrawable).getNumberOfLayers()).isEqualTo(2);
}
@Test
@UiThreadTest
public void onClick_firesAction() throws PendingIntent.CanceledException {
PendingIntent action = mock(PendingIntent.class);
QCTile tile = new QCTile.Builder().setChecked(false).setAction(action).build();
mView.onChanged(tile);
mView.findViewById(R.id.qc_tile_wrapper).performClick();
DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
assertThat(button.isChecked()).isTrue();
verify(action).send(any(Context.class), anyInt(), any(Intent.class));
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2021 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.car.qc.view;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.qc.QCList;
import com.android.car.qc.QCRow;
import com.android.car.qc.QCTile;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCViewTest {
private final Context mContext = ApplicationProvider.getApplicationContext();
private QCView mView;
@Before
public void setUp() {
mView = new QCView(mContext);
}
@Test
public void onChanged_null_noViews() {
mView.onChanged(null);
assertThat(mView.getChildCount()).isEqualTo(0);
}
@Test
public void onChanged_invalidType_throwsIllegalArgumentException() {
QCRow row = new QCRow.Builder().build();
assertThrows(IllegalArgumentException.class,
() -> mView.onChanged(row));
}
@Test
public void onChanged_list_createsListView() {
QCList list = new QCList.Builder().build();
mView.onChanged(list);
assertThat(mView.getChildCount()).isEqualTo(1);
assertThat(mView.getChildAt(0) instanceof QCListView).isTrue();
}
@Test
@UiThreadTest
public void onChanged_tile_createsTileView() {
QCTile tile = new QCTile.Builder().build();
mView.onChanged(tile);
assertThat(mView.getChildCount()).isEqualTo(1);
assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
}
@Test
@UiThreadTest
public void onChanged_alreadyHasView_callsOnChanged() {
QCTile tile = new QCTile.Builder().build();
mView.onChanged(tile);
assertThat(mView.getChildCount()).isEqualTo(1);
assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
QCTileView tileView = (QCTileView) mView.getChildAt(0);
ExtendedMockito.spyOn(tileView);
mView.onChanged(tile);
verify(tileView).onChanged(tile);
}
@Test
@UiThreadTest
public void setActionListener_setsOnChildView() {
QCTile tile = new QCTile.Builder().build();
mView.onChanged(tile);
assertThat(mView.getChildCount()).isEqualTo(1);
assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
QCTileView tileView = (QCTileView) mView.getChildAt(0);
ExtendedMockito.spyOn(tileView);
QCView.QCActionListener listener = mock(QCView.QCActionListener.class);
mView.setActionListener(listener);
ExtendedMockito.verify(tileView).setActionListener(listener);
}
}