diff options
author | Colin Marsch <colinmarsch@google.com> | 2020-05-19 18:49:00 +0000 |
---|---|---|
committer | Michael Bestas <mkbestas@lineageos.org> | 2021-10-09 17:32:05 +0300 |
commit | 2bde468b26c6779d28719845479e2ce20851e316 (patch) | |
tree | 130d58b2b542ebb1953c439d9c0ee1835078504f | |
parent | fd4efbae123b1e066fc12588b62c270d914e5917 (diff) |
DeskClock: Squashed AOSP changes before java app was removed.
* This includes all the java changes that were made to DeskClock
during the conversion to kotlin.
Squash of the following:
AOSP/DeskClock - Add Kotlin code for alarmclock/ files
Test: none currently, DeskClockKotlin target builds successfully, testing is a work in progress
BUG: 157255731
Original-Change-Id: I4c245d209ac733c4fb5ec527bc9ed84444b8cbd6
AOSP/DeskClock - Add Kotlin file for DataModel
Test: manual - Ran the following on Sargo phone. Tested all app
interactions (alarms, stopwatch, timer, cities, settings).
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin -j
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
BUG: 157255731
Original-Change-Id: I2fe53bbdddc9f473ece01bacb386e9ad08efcdc7
AOSP/DeskClock - Add Kotlin file for RingtonePickerActivity
Test: manual testing by running the following (1) on Sargo phone and testing
the UI, also unit tests ran as follows (2)
(1)
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin -j
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
(2)
$ make DeskClockTests
$ adb install
out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: I1eff3fe6e19a19aa35c6fbda541bce72b369b6ae
AOSP/DeskClock - Add Kotlin files for TimerService, SetupView, Receiver
Additionally, a small bug was fixed in the TimerModel and DataModel
Kotlin code (found by new unit tests).
Test: manual, ensuring the DeskClock UI is correct, also unit tests were
run as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin -j
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: I82a4836ab55281a73168d35ea4f50d39279ce5da
AOSP/DeskClock - Add Kotlin files for Timer classes
Test: manual, ensuring the DeskClock UI and Timers function correctly,
as well as unit tests, ran as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin -j
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: Idb33290c6bf11ee6147f547c85b5f6e3859c17b7
AOSP/DeskClock - Add Kotlin file for ExpiredTimersActivity
Test: manual, tested the DeskClock UI including running timers to
expiration. Also ran the unit tests as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: I93ec96bae36dc353c0c252c0ef6769965a7172e7
AOSP/DeskClock - Add Kotlin file for TimerFragment
Test: manual, tested the DeskClock UI and Timers specifically. As well,
unit tests were ran as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: Ib3769584161d6e712a8eafde977f58b3c55f69e3
AOSP/DeskClock - Add Kotlin for FormattedStringModel, PeriodicCallbackModel
Test: manual - Tested the DeskClock UI. As well, unit tests were ran as
follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests
BUG: 157255731
Original-Change-Id: I7cc4fb5aa20cd32be51e65de1b2b6a12a7cfa795
AOSP/DeskClock - Add Kotlin for Tab related UI Data files
Test: manual - Tested the DeskClock UI and tab interactions. As well,
unit tests were run as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ make DeskClockTests
$ adb install out/target/product/sargo/testcases/DeskClockTests/arm64/DeskClockTests.apk
$ adb shell am instrument -w com.android.deskclock.tests`
BUG: 157255731
Original-Change-Id: Ibd7871edf20e548a1cd548246f0a6be88f5cfb88
AOSP/DeskClock - Add Kotlin file for CitySelectionActivity
Test: manual - tested DeskClock UI. As well, tests were run as follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ atest DeskClockTests
BUG: 157255731
Original-Change-Id: I3bd0952f753130e655d1b9f3c911863f34e6e41a
AOSP/DeskClock - Add Kotlin files for BaseActivity, CircleButtonsLayout
Test: manual - Tested the DeskClock UI. As well, tests were run as
follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ atest DeskClockTests
BUG: 157255731
Original-Change-Id: I019825f173305756633389a26a0bb0e973f2172c
AOSP/DeskClock - Add Kotlin file for DeskClock class
The onNewIntent call in TimerFragmentTest was changed to setIntent since the
visibility of the protected onNewIntent method in Kotlin was more
restrictive than before.
Test: manual - Tested the DeskClock UI. As well tests were ran as
follows
$ source build/envsetup.sh
$ lunch aosp_sargo-userdebug
$ make DeskClockKotlin
$ adb install out/target/product/sargo/product/app/DeskClockKotlin/DeskClockKotlin.apk
$ atest DeskClockTests
BUG: 157255731
Original-Change-Id: I9711dd8068a29fc0a25ee1f364ecb1b6ef326d6a
Change-Id: I3d63f95cffc257734f803683d477bf3ecf88f208
25 files changed, 2213 insertions, 78 deletions
diff --git a/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java index 849e8cf80..b0aba67d2 100644 --- a/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java +++ b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java @@ -93,7 +93,7 @@ public class DigitalAppWidgetCityViewsFactory implements RemoteViewsFactory { final int worldClockCount = mCities.size(); final double totalClockCount = homeClockCount + worldClockCount; - // number of clocks / 2 clocks per row + // Number of clocks / 2 clocks per row return (int) Math.ceil(totalClockCount / 2); } @@ -216,7 +216,6 @@ public class DigitalAppWidgetCityViewsFactory implements RemoteViewsFactory { * occur on the main thread. */ private static final class RefreshRunnable implements Runnable { - private City mHomeCity; private List<City> mCities; private boolean mShowHomeClock; diff --git a/src/com/android/alarmclock/DigitalAppWidgetProvider.java b/src/com/android/alarmclock/DigitalAppWidgetProvider.java index 3be07eca7..80a724e85 100644 --- a/src/com/android/alarmclock/DigitalAppWidgetProvider.java +++ b/src/com/android/alarmclock/DigitalAppWidgetProvider.java @@ -181,7 +181,7 @@ public class DigitalAppWidgetProvider extends AppWidgetProvider { Bundle options) { super.onAppWidgetOptionsChanged(context, wm, widgetId, options); - // scale the fonts of the clock to fit inside the new size + // Scale the fonts of the clock to fit inside the new size relayoutWidget(context, AppWidgetManager.getInstance(context), widgetId, options); } diff --git a/src/com/android/deskclock/AlarmClockFragment.java b/src/com/android/deskclock/AlarmClockFragment.java index 70ffee3a5..bf4852855 100644 --- a/src/com/android/deskclock/AlarmClockFragment.java +++ b/src/com/android/deskclock/AlarmClockFragment.java @@ -198,7 +198,7 @@ public final class AlarmClockFragment extends DeskClockFragment implements // Schedule a runnable to update the "Today/Tomorrow" values displayed for non-repeating // alarms when midnight passes. - UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater, 100); + UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater); // Check if another app asked us to create a blank new alarm. final Intent intent = getActivity().getIntent(); diff --git a/src/com/android/deskclock/CircleButtonsLayout.java b/src/com/android/deskclock/CircleButtonsLayout.java index 079472e9a..c77a823e6 100644 --- a/src/com/android/deskclock/CircleButtonsLayout.java +++ b/src/com/android/deskclock/CircleButtonsLayout.java @@ -9,7 +9,7 @@ import android.widget.FrameLayout; import android.widget.TextView; /** - * This class adjusts the locations of children buttons and text of this view group by adjusting the + * This class adjusts the locations of child buttons and text of this view group by adjusting the * margins of each item. The left and right buttons are aligned with the bottom of the circle. The * stop button and label text are located within the circle with the stop button near the bottom and * the label text near the top. The maximum text size for the label text view is also calculated. diff --git a/src/com/android/deskclock/ClockFragment.java b/src/com/android/deskclock/ClockFragment.java index b487e52be..0ab1f4e9d 100644 --- a/src/com/android/deskclock/ClockFragment.java +++ b/src/com/android/deskclock/ClockFragment.java @@ -135,7 +135,7 @@ public final class ClockFragment extends DeskClockFragment { } // Schedule a runnable to update the date every quarter hour. - UiDataModel.getUiDataModel().addQuarterHourCallback(mQuarterHourUpdater, 100); + UiDataModel.getUiDataModel().addQuarterHourCallback(mQuarterHourUpdater); return fragmentView; } @@ -421,7 +421,7 @@ public final class ClockFragment extends DeskClockFragment { } private List<City> getCities() { - return DataModel.getDataModel().getSelectedCities(); + return (List<City>) DataModel.getDataModel().getSelectedCities(); } private void refreshAlarm() { diff --git a/src/com/android/deskclock/Screensaver.java b/src/com/android/deskclock/Screensaver.java index 68899d96e..49530d44c 100644 --- a/src/com/android/deskclock/Screensaver.java +++ b/src/com/android/deskclock/Screensaver.java @@ -133,7 +133,7 @@ public final class Screensaver extends DreamService { Utils.refreshAlarm(this, mContentView); startPositionUpdater(); - UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater, 100); + UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater); } @Override diff --git a/src/com/android/deskclock/ScreensaverActivity.java b/src/com/android/deskclock/ScreensaverActivity.java index c7d54af3f..e1db8c5ff 100644 --- a/src/com/android/deskclock/ScreensaverActivity.java +++ b/src/com/android/deskclock/ScreensaverActivity.java @@ -164,7 +164,7 @@ public class ScreensaverActivity extends BaseActivity { Utils.refreshAlarm(ScreensaverActivity.this, mContentView); startPositionUpdater(); - UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater, 100); + UiDataModel.getUiDataModel().addMidnightCallback(mMidnightUpdater); final Intent intent = registerReceiver(null, new IntentFilter(ACTION_BATTERY_CHANGED)); final boolean pluggedIn = intent != null && intent.getIntExtra(EXTRA_PLUGGED, 0) != 0; diff --git a/src/com/android/deskclock/data/DataModel.java b/src/com/android/deskclock/data/DataModel.java index ec49b8d86..3dafe9ddb 100644 --- a/src/com/android/deskclock/data/DataModel.java +++ b/src/com/android/deskclock/data/DataModel.java @@ -24,9 +24,11 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; -import androidx.annotation.StringRes; import android.view.View; +import androidx.annotation.Keep; +import androidx.annotation.StringRes; + import com.android.deskclock.Predicate; import com.android.deskclock.R; import com.android.deskclock.Utils; @@ -522,6 +524,7 @@ public final class DataModel { * @param timer the timer to be reset * @return the reset {@code timer} */ + @Keep public Timer resetTimer(Timer timer) { enforceMainLooper(); return mTimerModel.resetTimer(timer, false /* allowDelete */, 0 /* eventLabelId */); diff --git a/src/com/android/deskclock/timer/TimerService.java b/src/com/android/deskclock/timer/TimerService.java index a82652e16..63f45c2cc 100644 --- a/src/com/android/deskclock/timer/TimerService.java +++ b/src/com/android/deskclock/timer/TimerService.java @@ -114,22 +114,25 @@ public final class TimerService extends Service { try { final String action = intent.getAction(); final int label = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, R.string.label_intent); - switch (action) { - case ACTION_UPDATE_NOTIFICATION: { - DataModel.getDataModel().updateTimerNotification(); - return START_NOT_STICKY; - } - case ACTION_RESET_EXPIRED_TIMERS: { - DataModel.getDataModel().resetOrDeleteExpiredTimers(label); - return START_NOT_STICKY; - } - case ACTION_RESET_UNEXPIRED_TIMERS: { - DataModel.getDataModel().resetUnexpiredTimers(label); - return START_NOT_STICKY; - } - case ACTION_RESET_MISSED_TIMERS: { - DataModel.getDataModel().resetMissedTimers(label); - return START_NOT_STICKY; + + if (action != null) { + switch (action) { + case ACTION_UPDATE_NOTIFICATION: { + DataModel.getDataModel().updateTimerNotification(); + return START_NOT_STICKY; + } + case ACTION_RESET_EXPIRED_TIMERS: { + DataModel.getDataModel().resetOrDeleteExpiredTimers(label); + return START_NOT_STICKY; + } + case ACTION_RESET_UNEXPIRED_TIMERS: { + DataModel.getDataModel().resetUnexpiredTimers(label); + return START_NOT_STICKY; + } + case ACTION_RESET_MISSED_TIMERS: { + DataModel.getDataModel().resetMissedTimers(label); + return START_NOT_STICKY; + } } } @@ -143,39 +146,41 @@ public final class TimerService extends Service { } // Perform the action on the timer. - switch (action) { - case ACTION_SHOW_TIMER: { - Events.sendTimerEvent(R.string.action_show, label); - - // Change to the timers tab. - UiDataModel.getUiDataModel().setSelectedTab(TIMERS); - - // Open DeskClock which is now positioned on the timers tab and show the timer - // in question. - final Intent showTimers = new Intent(this, DeskClock.class) - .putExtra(EXTRA_TIMER_ID, timerId) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(showTimers); - break; - } case ACTION_START_TIMER: { - Events.sendTimerEvent(R.string.action_start, label); - DataModel.getDataModel().startTimer(this, timer); - break; - } case ACTION_PAUSE_TIMER: { - Events.sendTimerEvent(R.string.action_pause, label); - DataModel.getDataModel().pauseTimer(timer); - break; - } case ACTION_ADD_MINUTE_TIMER: { - Events.sendTimerEvent(R.string.action_add_minute, label); - DataModel.getDataModel().addTimerMinute(timer); - break; - } case ACTION_RESET_TIMER: { - DataModel.getDataModel().resetOrDeleteTimer(timer, label); - break; - } case ACTION_TIMER_EXPIRED: { - Events.sendTimerEvent(R.string.action_fire, label); - DataModel.getDataModel().expireTimer(this, timer); - break; + if (action != null) { + switch (action) { + case ACTION_SHOW_TIMER: { + Events.sendTimerEvent(R.string.action_show, label); + + // Change to the timers tab. + UiDataModel.getUiDataModel().setSelectedTab(TIMERS); + + // Open DeskClock which is now positioned on the timers tab and show + // the timer in question. + final Intent showTimers = new Intent(this, DeskClock.class) + .putExtra(EXTRA_TIMER_ID, timerId) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(showTimers); + break; + } case ACTION_START_TIMER: { + Events.sendTimerEvent(R.string.action_start, label); + DataModel.getDataModel().startTimer(this, timer); + break; + } case ACTION_PAUSE_TIMER: { + Events.sendTimerEvent(R.string.action_pause, label); + DataModel.getDataModel().pauseTimer(timer); + break; + } case ACTION_ADD_MINUTE_TIMER: { + Events.sendTimerEvent(R.string.action_add_minute, label); + DataModel.getDataModel().addTimerMinute(timer); + break; + } case ACTION_RESET_TIMER: { + DataModel.getDataModel().resetOrDeleteTimer(timer, label); + break; + } case ACTION_TIMER_EXPIRED: { + Events.sendTimerEvent(R.string.action_fire, label); + DataModel.getDataModel().expireTimer(this, timer); + break; + } } } } finally { diff --git a/src/com/android/deskclock/uidata/PeriodicCallbackModel.java b/src/com/android/deskclock/uidata/PeriodicCallbackModel.java index a8232e315..095955a22 100644 --- a/src/com/android/deskclock/uidata/PeriodicCallbackModel.java +++ b/src/com/android/deskclock/uidata/PeriodicCallbackModel.java @@ -82,26 +82,29 @@ final class PeriodicCallbackModel { /** * @param runnable to be called every quarter-hour - * @param offset an offset applied to the quarter-hour to control when the callback occurs */ - void addQuarterHourCallback(Runnable runnable, long offset) { - addPeriodicCallback(runnable, Period.QUARTER_HOUR, offset); + void addQuarterHourCallback(Runnable runnable) { + // Callbacks *can* occur early so pad in an extra 100ms on the quarter-hour callback + // to ensure the sampled wallclock time reflects the subsequent quarter-hour. + addPeriodicCallback(runnable, Period.QUARTER_HOUR, 100L); } /** * @param runnable to be called every hour - * @param offset an offset applied to the hour to control when the callback occurs */ - void addHourCallback(Runnable runnable, long offset) { - addPeriodicCallback(runnable, Period.HOUR, offset); + void addHourCallback(Runnable runnable) { + // Callbacks *can* occur early so pad in an extra 100ms on the hour callback to ensure + // the sampled wallclock time reflects the subsequent hour. + addPeriodicCallback(runnable, Period.HOUR, 100L); } /** * @param runnable to be called every midnight - * @param offset an offset applied to the midnight to control when the callback occurs */ - void addMidnightCallback(Runnable runnable, long offset) { - addPeriodicCallback(runnable, Period.MIDNIGHT, offset); + void addMidnightCallback(Runnable runnable) { + // Callbacks *can* occur early so pad in an extra 100ms on the midnight callback to ensure + // the sampled wallclock time reflects the subsequent day. + addPeriodicCallback(runnable, Period.MIDNIGHT, 100L); } /** diff --git a/src/com/android/deskclock/uidata/UiDataModel.java b/src/com/android/deskclock/uidata/UiDataModel.java index 3d592d405..c0d780696 100644 --- a/src/com/android/deskclock/uidata/UiDataModel.java +++ b/src/com/android/deskclock/uidata/UiDataModel.java @@ -338,29 +338,26 @@ public final class UiDataModel { /** * @param runnable to be called every quarter-hour - * @param offset an offset applied to the quarter-hour to control when the callback occurs */ - public void addQuarterHourCallback(Runnable runnable, long offset) { + public void addQuarterHourCallback(Runnable runnable) { enforceMainLooper(); - mPeriodicCallbackModel.addQuarterHourCallback(runnable, offset); + mPeriodicCallbackModel.addQuarterHourCallback(runnable); } /** * @param runnable to be called every hour - * @param offset an offset applied to the hour to control when the callback occurs */ - public void addHourCallback(Runnable runnable, long offset) { + public void addHourCallback(Runnable runnable) { enforceMainLooper(); - mPeriodicCallbackModel.addHourCallback(runnable, offset); + mPeriodicCallbackModel.addHourCallback(runnable); } /** * @param runnable to be called every midnight - * @param offset an offset applied to the midnight to control when the callback occurs */ - public void addMidnightCallback(Runnable runnable, long offset) { + public void addMidnightCallback(Runnable runnable) { enforceMainLooper(); - mPeriodicCallbackModel.addMidnightCallback(runnable, offset); + mPeriodicCallbackModel.addMidnightCallback(runnable); } /** diff --git a/src/com/android/deskclock/worldclock/CitySelectionActivity.java b/src/com/android/deskclock/worldclock/CitySelectionActivity.java index d8954d095..13e85c47d 100644 --- a/src/com/android/deskclock/worldclock/CitySelectionActivity.java +++ b/src/com/android/deskclock/worldclock/CitySelectionActivity.java @@ -497,7 +497,7 @@ public final class CitySelectionActivity extends BaseActivity { mIs24HoursMode = DateFormat.is24HourFormat(mContext); // Refresh the user selections. - final List<City> selected = DataModel.getDataModel().getSelectedCities(); + final List<City> selected = (List<City>) DataModel.getDataModel().getSelectedCities(); mUserSelectedCities.clear(); mUserSelectedCities.addAll(selected); mOriginalUserSelectionCount = selected.size(); diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 000000000..c8e807e6b --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,17 @@ +android_test { + name: "DeskClockTests", + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "junit", + "androidx.test.core", + "androidx.test.runner", + "androidx.test.rules", + ], + // Include all test java files. + srcs: ["src/**/*.java"], + platform_apis: true, + instrumentation_for: "DeskClock", +} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 000000000..64cc6e2e5 --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.deskclock.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.deskclock" + android:label="Tests for DeskClock application."/> +</manifest> diff --git a/tests/src/com/android/deskclock/ringtone/RingtonePickerActivityTest.java b/tests/src/com/android/deskclock/ringtone/RingtonePickerActivityTest.java new file mode 100644 index 000000000..1b4bd2f9d --- /dev/null +++ b/tests/src/com/android/deskclock/ringtone/RingtonePickerActivityTest.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2020 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.deskclock.ringtone; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.preference.PreferenceManager; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.test.InstrumentationRegistry; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.rule.ActivityTestRule; + +import com.android.deskclock.ItemAdapter; +import com.android.deskclock.ItemAdapter.ItemHolder; +import com.android.deskclock.R; +import com.android.deskclock.Utils; +import com.android.deskclock.data.DataModel; +import com.android.deskclock.provider.Alarm; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Iterator; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Exercise the user interface that adjusts the selected ringtone. + */ +@RunWith(AndroidJUnit4ClassRunner.class) +public class RingtonePickerActivityTest { + + private RingtonePickerActivity activity; + private RecyclerView ringtoneList; + private ItemAdapter<ItemHolder<Uri>> ringtoneAdapter; + + public static final Uri ALERT = Uri.parse("content://settings/system/alarm_alert"); + public static final Uri CUSTOM_RINGTONE_1 = Uri.parse("content://media/external/audio/one.ogg"); + + @Rule + public ActivityTestRule<RingtonePickerActivity> rule = + new ActivityTestRule<>(RingtonePickerActivity.class, true, false); + + @Before + @After + public void setUp() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit().clear().commit(); + } + + @Test + public void validateDefaultState_TimerRingtonePicker() { + createTimerRingtonePickerActivity(); + + final List<ItemHolder<Uri>> systemRingtoneHolders = ringtoneAdapter.getItems(); + + assertEquals(28, systemRingtoneHolders.size()); + final Iterator<ItemHolder<Uri>> itemsIter = systemRingtoneHolders.iterator(); + + final HeaderHolder filesHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.your_sounds, filesHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, filesHeaderHolder.getItemViewType()); + + final AddCustomRingtoneHolder addNewHolder = (AddCustomRingtoneHolder) itemsIter.next(); + assertEquals(AddCustomRingtoneViewHolder.VIEW_TYPE_ADD_NEW, addNewHolder.getItemViewType()); + + final HeaderHolder systemHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.device_sounds, systemHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, systemHeaderHolder.getItemViewType()); + + final RingtoneHolder silentHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(Utils.RINGTONE_SILENT, silentHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, silentHolder.getItemViewType()); + + final RingtoneHolder defaultHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, defaultHolder.getItemViewType()); + + Runnable assertRunnable = () -> { + assertEquals("Silent", silentHolder.getName()); + assertEquals("Timer Expired", defaultHolder.getName()); + assertEquals(DataModel.getDataModel().getDefaultTimerRingtoneUri(), + defaultHolder.getUri()); + // Verify initial selection. + assertEquals( + DataModel.getDataModel().getTimerRingtoneUri(), + DataModel.getDataModel().getDefaultTimerRingtoneUri()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(assertRunnable); + } + + @Test + public void validateDefaultState_AlarmRingtonePicker() { + createAlarmRingtonePickerActivity(ALERT); + + final List<ItemHolder<Uri>> systemRingtoneHolders = ringtoneAdapter.getItems(); + + assertEquals(28, systemRingtoneHolders.size()); + final Iterator<ItemHolder<Uri>> itemsIter = systemRingtoneHolders.iterator(); + + final HeaderHolder filesHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.your_sounds, filesHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, filesHeaderHolder.getItemViewType()); + + final AddCustomRingtoneHolder addNewHolder = (AddCustomRingtoneHolder) itemsIter.next(); + assertEquals(AddCustomRingtoneViewHolder.VIEW_TYPE_ADD_NEW, addNewHolder.getItemViewType()); + + final HeaderHolder systemHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.device_sounds, systemHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, systemHeaderHolder.getItemViewType()); + + final RingtoneHolder silentHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(Utils.RINGTONE_SILENT, silentHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, silentHolder.getItemViewType()); + + final RingtoneHolder defaultHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, defaultHolder.getItemViewType()); + + Runnable assertRunnable = () -> { + assertEquals("Silent", silentHolder.getName()); + assertEquals("Default alarm sound", defaultHolder.getName()); + assertEquals(DataModel.getDataModel().getDefaultAlarmRingtoneUri(), + defaultHolder.getUri()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(assertRunnable); + } + + @Test + public void validateDefaultState_TimerRingtonePicker_WithCustomRingtones() { + Runnable customRingtoneRunnable = () -> { + DataModel.getDataModel().addCustomRingtone(CUSTOM_RINGTONE_1, "CustomSound"); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(customRingtoneRunnable); + createTimerRingtonePickerActivity(); + + final List<ItemHolder<Uri>> systemRingtoneHolders = ringtoneAdapter.getItems(); + + assertEquals(29, systemRingtoneHolders.size()); + final Iterator<ItemHolder<Uri>> itemsIter = systemRingtoneHolders.iterator(); + + final HeaderHolder filesHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.your_sounds, filesHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, filesHeaderHolder.getItemViewType()); + + final CustomRingtoneHolder customRingtoneHolder = (CustomRingtoneHolder) itemsIter.next(); + assertEquals("CustomSound", customRingtoneHolder.getName()); + assertEquals(CUSTOM_RINGTONE_1, customRingtoneHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_CUSTOM_SOUND, + customRingtoneHolder.getItemViewType()); + + final AddCustomRingtoneHolder addNewHolder = (AddCustomRingtoneHolder) itemsIter.next(); + assertEquals(AddCustomRingtoneViewHolder.VIEW_TYPE_ADD_NEW, addNewHolder.getItemViewType()); + + final HeaderHolder systemHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.device_sounds, systemHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, systemHeaderHolder.getItemViewType()); + + final RingtoneHolder silentHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(Utils.RINGTONE_SILENT, silentHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, silentHolder.getItemViewType()); + + final RingtoneHolder defaultHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, defaultHolder.getItemViewType()); + + Runnable assertRunnable = () -> { + assertEquals("Silent", silentHolder.getName()); + assertEquals("Timer Expired", defaultHolder.getName()); + assertEquals(DataModel.getDataModel().getDefaultTimerRingtoneUri(), + defaultHolder.getUri()); + // Verify initial selection. + assertEquals( + DataModel.getDataModel().getTimerRingtoneUri(), + DataModel.getDataModel().getDefaultTimerRingtoneUri()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(assertRunnable); + + Runnable removeCustomRingtoneRunnable = () -> { + DataModel.getDataModel().removeCustomRingtone(CUSTOM_RINGTONE_1); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(removeCustomRingtoneRunnable); + } + + @Test + public void validateDefaultState_AlarmRingtonePicker_WithCustomRingtones() { + Runnable customRingtoneRunnable = () -> { + DataModel.getDataModel().addCustomRingtone(CUSTOM_RINGTONE_1, "CustomSound"); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(customRingtoneRunnable); + createAlarmRingtonePickerActivity(ALERT); + + final List<ItemHolder<Uri>> systemRingtoneHolders = ringtoneAdapter.getItems(); + + assertEquals(29, systemRingtoneHolders.size()); + final Iterator<ItemHolder<Uri>> itemsIter = systemRingtoneHolders.iterator(); + + final HeaderHolder filesHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.your_sounds, filesHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, filesHeaderHolder.getItemViewType()); + + final CustomRingtoneHolder customRingtoneHolder = (CustomRingtoneHolder) itemsIter.next(); + assertEquals("CustomSound", customRingtoneHolder.getName()); + assertEquals(CUSTOM_RINGTONE_1, customRingtoneHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_CUSTOM_SOUND, + customRingtoneHolder.getItemViewType()); + + final AddCustomRingtoneHolder addNewHolder = (AddCustomRingtoneHolder) itemsIter.next(); + assertEquals(AddCustomRingtoneViewHolder.VIEW_TYPE_ADD_NEW, addNewHolder.getItemViewType()); + + final HeaderHolder systemHeaderHolder = (HeaderHolder) itemsIter.next(); + assertEquals(R.string.device_sounds, systemHeaderHolder.getTextResId()); + assertEquals(HeaderViewHolder.VIEW_TYPE_ITEM_HEADER, systemHeaderHolder.getItemViewType()); + + final RingtoneHolder silentHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(Utils.RINGTONE_SILENT, silentHolder.getUri()); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, silentHolder.getItemViewType()); + + final RingtoneHolder defaultHolder = (RingtoneHolder) itemsIter.next(); + assertEquals(RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND, defaultHolder.getItemViewType()); + + Runnable assertRunnable = () -> { + assertEquals("Silent", silentHolder.getName()); + assertEquals("Default alarm sound", defaultHolder.getName()); + assertEquals(DataModel.getDataModel().getDefaultAlarmRingtoneUri(), + defaultHolder.getUri()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(assertRunnable); + + Runnable removeCustomRingtoneRunnable = () -> { + DataModel.getDataModel().removeCustomRingtone(CUSTOM_RINGTONE_1); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(removeCustomRingtoneRunnable); + } + + private void createTimerRingtonePickerActivity() { + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final Intent newIntent = new Intent(); + + Runnable createIntentRunnable = () -> { + final Intent intent = RingtonePickerActivity.createTimerRingtonePickerIntent(context); + newIntent.fillIn(intent, 0); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(createIntentRunnable); + + createRingtonePickerActivity(newIntent); + } + + private void createAlarmRingtonePickerActivity(Uri ringtone) { + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final Intent newIntent = new Intent(); + + Runnable createIntentRunnable = () -> { + // Use the custom ringtone in some alarms. + final Alarm alarm = new Alarm(1, 1); + alarm.enabled = true; + alarm.vibrate = true; + alarm.alert = ringtone; + alarm.deleteAfterUse = true; + + final Intent intent = + RingtonePickerActivity.createAlarmRingtonePickerIntent(context, alarm); + newIntent.fillIn(intent, 0); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(createIntentRunnable); + + createRingtonePickerActivity(newIntent); + } + + @SuppressWarnings("unchecked") + private void createRingtonePickerActivity(Intent intent) { + activity = rule.launchActivity(intent); + ringtoneList = activity.findViewById(R.id.ringtone_content); + ringtoneAdapter = (ItemAdapter<ItemHolder<Uri>>) ringtoneList.getAdapter(); + } +} diff --git a/tests/src/com/android/deskclock/timer/ExpiredTimersActivityTest.java b/tests/src/com/android/deskclock/timer/ExpiredTimersActivityTest.java new file mode 100644 index 000000000..1fd7b0f9c --- /dev/null +++ b/tests/src/com/android/deskclock/timer/ExpiredTimersActivityTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 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.deskclock.timer; + +import android.content.Context; +import android.content.Intent; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.rule.ActivityTestRule; + +import com.android.deskclock.data.DataModel; +import com.android.deskclock.data.Timer; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertSame; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class ExpiredTimersActivityTest { + + @Rule + public ActivityTestRule<ExpiredTimersActivity> rule = + new ActivityTestRule<>(ExpiredTimersActivity.class, true, false); + + @Test + public void configurationChanges_DoNotResetFiringTimer() { + // Construct an ExpiredTimersActivity to display the firing timer. + final Context context = ApplicationProvider.getApplicationContext(); + final Intent intent = new Intent() + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + final ExpiredTimersActivity activity = rule.launchActivity(intent); + + Runnable fireTimerRunnable = () -> { + // Create a firing timer. + final DataModel dm = DataModel.getDataModel(); + Timer timer = dm.addTimer(60000L, "", false); + dm.startTimer(timer); + dm.expireTimer(null, dm.getTimer(timer.getId())); + timer = dm.getTimer(timer.getId()); + + // Make the ExpiredTimersActivity believe it has been displayed to the user. + activity.getWindow().getCallback().onWindowFocusChanged(true); + + // Simulate a configuration change by recreating the activity. + activity.recreate(); + + // Verify that the recreation did not alter the firing timer. + assertSame(timer, dm.getTimer(timer.getId())); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(fireTimerRunnable); + } +} diff --git a/tests/src/com/android/deskclock/timer/TimerFragmentTest.java b/tests/src/com/android/deskclock/timer/TimerFragmentTest.java new file mode 100644 index 000000000..9728db633 --- /dev/null +++ b/tests/src/com/android/deskclock/timer/TimerFragmentTest.java @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2020 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.deskclock.timer; + +import android.content.Context; +import android.content.Intent; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.rule.ActivityTestRule; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.android.deskclock.DeskClock; +import com.android.deskclock.R; +import com.android.deskclock.data.DataModel; +import com.android.deskclock.data.Timer; +import com.android.deskclock.widget.MockFabContainer; + +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class TimerFragmentTest { + + private static int LIGHT; + private static int DARK; + private static int TOP; + private static int BOTTOM; + + private static final int GONE = 0; + + private TimerFragment fragment; + private View timersView; + private View timerSetupView; + private ViewPager viewPager; + private TimerPagerAdapter adapter; + + private ImageView fab; + private Button leftButton; + private Button rightButton; + + @Rule + public ActivityTestRule<DeskClock> rule = new ActivityTestRule<>(DeskClock.class, true); + + @BeforeClass + public static void staticSetUp() { + LIGHT = R.drawable.ic_swipe_circle_light; + DARK = R.drawable.ic_swipe_circle_dark; + TOP = R.drawable.ic_swipe_circle_top; + BOTTOM = R.drawable.ic_swipe_circle_bottom; + } + + private void setUpSingleTimer() { + Runnable addTimerRunnable = () -> { + DataModel.getDataModel().addTimer(60000L, null, false); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + setUpFragment(); + } + + private void setUpTwoTimers() { + Runnable addTimerRunnable = () -> { + DataModel.getDataModel().addTimer(60000L, null, false); + DataModel.getDataModel().addTimer(90000L, null, false); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + setUpFragment(); + } + + private void setUpFragment() { + Runnable setUpFragmentRunnable = () -> { + ViewPager deskClockPager = + (ViewPager) rule.getActivity().findViewById(R.id.desk_clock_pager); + PagerAdapter tabPagerAdapter = (PagerAdapter) deskClockPager.getAdapter(); + fragment = (TimerFragment) tabPagerAdapter.instantiateItem(deskClockPager, 2); + fragment.onStart(); + fragment.selectTab(); + final MockFabContainer fabContainer = + new MockFabContainer(fragment, ApplicationProvider.getApplicationContext()); + fragment.setFabContainer(fabContainer); + + final View view = fragment.getView(); + assertNotNull(view); + + timersView = view.findViewById(R.id.timer_view); + timerSetupView = view.findViewById(R.id.timer_setup); + viewPager = view.findViewById(R.id.vertical_view_pager); + adapter = (TimerPagerAdapter) viewPager.getAdapter(); + + fab = fabContainer.getFab(); + leftButton = fabContainer.getLeftButton(); + rightButton = fabContainer.getRightButton(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(setUpFragmentRunnable); + } + + @After + public void tearDown() { + clearTimers(); + fragment = null; + fab = null; + timerSetupView = null; + timersView = null; + adapter = null; + viewPager = null; + leftButton = null; + rightButton = null; + } + + private void clearTimers() { + Runnable clearTimersRunnable = () -> { + final List<Timer> timers = new ArrayList<>(DataModel.getDataModel().getTimers()); + for (Timer timer : timers) { + DataModel.getDataModel().removeTimer(timer); + } + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(clearTimersRunnable); + } + + @Test + public void initialStateNoTimers() { + setUpFragment(); + assertEquals(View.VISIBLE, timerSetupView.getVisibility()); + assertEquals(View.GONE, timersView.getVisibility()); + assertAdapter(0); + } + + @Test + public void initialStateOneTimer() { + setUpSingleTimer(); + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + assertAdapter(1); + } + + @Test + public void initialStateTwoTimers() { + setUpTwoTimers(); + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + assertAdapter(2); + } + + @Test + public void timeClick_startsTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 0); + } + + @Test + public void timeClick_startsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(1); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void timeClick_pausesTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 0); + clickView(timeText); + assertStateEquals(Timer.State.PAUSED, 0); + } + + @Test + public void timeClick_pausesSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(1); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.PAUSED, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void timeClick_restartsTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 0); + clickView(timeText); + assertStateEquals(Timer.State.PAUSED, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 0); + } + + @Test + public void timeClick_restartsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(1); + final TextView timeText = timerItem.findViewById(R.id.timer_time_text); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.PAUSED, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(timeText); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void fabClick_startsTimer() { + setUpSingleTimer(); + + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + } + + @Test + public void fabClick_startsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void fabClick_pausesTimer() { + setUpSingleTimer(); + + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 0); + } + + @Test + public void fabClick_pausesSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void fabClick_restartsTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + } + + @Test + public void fabClick_restartsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void fabClick_resetsTimer() { + setUpSingleTimer(); + + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + final Context context = fab.getContext(); + Runnable expireTimerRunnable = () -> { + DataModel.getDataModel().expireTimer(null, DataModel.getDataModel().getTimers().get(0)); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(expireTimerRunnable); + clickFab(); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void fabClick_resetsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + final Context context = fab.getContext(); + Runnable expireTimerRunnable = () -> { + DataModel.getDataModel().expireTimer(null, DataModel.getDataModel().getTimers().get(1)); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(expireTimerRunnable); + clickFab(); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void clickAdd_addsOneMinuteToTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final Button addMinute = timerItem.findViewById(R.id.reset_add); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + Runnable getTimersRunnable = () -> { + long remainingTime1 = DataModel.getDataModel().getTimers().get(0).getRemainingTime(); + addMinute.performClick(); + long remainingTime2 = DataModel.getDataModel().getTimers().get(0).getRemainingTime(); + assertSame(Timer.State.RUNNING, DataModel.getDataModel().getTimers().get(0).getState()); + long expectedSeconds = + TimeUnit.MILLISECONDS.toSeconds(remainingTime1 + DateUtils.MINUTE_IN_MILLIS); + long observedSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingTime2); + assertEquals(expectedSeconds, observedSeconds); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(getTimersRunnable); + } + + @Test + public void clickAdd_addsOneMinuteToSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(1); + final Button addMinute = timerItem.findViewById(R.id.reset_add); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + Runnable getTimersRunnable = () -> { + long remainingTime1 = DataModel.getDataModel().getTimers().get(1).getRemainingTime(); + addMinute.performClick(); + long remainingTime2 = DataModel.getDataModel().getTimers().get(1).getRemainingTime(); + assertSame(Timer.State.RUNNING, DataModel.getDataModel().getTimers().get(1).getState()); + assertSame(Timer.State.RESET, DataModel.getDataModel().getTimers().get(0).getState()); + long expectedSeconds = + TimeUnit.MILLISECONDS.toSeconds(remainingTime1 + DateUtils.MINUTE_IN_MILLIS); + long observedSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingTime2); + assertEquals(expectedSeconds, observedSeconds); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(getTimersRunnable); + } + + @Test + public void clickReset_resetsTimer() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final Button reset = timerItem.findViewById(R.id.reset_add); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 0); + clickView(reset); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void clickReset_resetsSecondTimer() { + setUpTwoTimers(); + + setCurrentItem(1); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(1); + final Button reset = timerItem.findViewById(R.id.reset_add); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.RUNNING, 1); + assertStateEquals(Timer.State.RESET, 0); + clickFab(); + assertStateEquals(Timer.State.PAUSED, 1); + assertStateEquals(Timer.State.RESET, 0); + clickView(reset); + assertStateEquals(Timer.State.RESET, 1); + assertStateEquals(Timer.State.RESET, 0); + } + + @Test + public void labelClick_opensLabel() { + setUpSingleTimer(); + + setCurrentItem(0); + final TimerItem timerItem = (TimerItem) viewPager.getChildAt(0); + final TextView label = timerItem.findViewById(R.id.timer_label); + assertStateEquals(Timer.State.RESET, 0); + clickView(label); + } + + // + // 3 Indicators + // + + @Test + public void verify3Indicators0Pages() { + assertIndicatorsEquals(0, 3, 0, GONE, GONE, GONE); + } + + @Test + public void verify3Indicators1Page() { + assertIndicatorsEquals(0, 3, 1, GONE, GONE, GONE); + } + + @Test + public void verify3Indicators2Pages() { + assertIndicatorsEquals(0, 3, 2, LIGHT, DARK, GONE); + assertIndicatorsEquals(1, 3, 2, DARK, LIGHT, GONE); + } + + @Test + public void verify3Indicators3Pages() { + assertIndicatorsEquals(0, 3, 3, LIGHT, DARK, DARK); + assertIndicatorsEquals(1, 3, 3, DARK, LIGHT, DARK); + assertIndicatorsEquals(2, 3, 3, DARK, DARK, LIGHT); + } + + @Test + public void verify3Indicators4Pages() { + assertIndicatorsEquals(0, 3, 4, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(1, 3, 4, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(2, 3, 4, TOP, LIGHT, DARK); + assertIndicatorsEquals(3, 3, 4, TOP, DARK, LIGHT); + } + + @Test + public void verify3Indicators5Pages() { + assertIndicatorsEquals(0, 3, 5, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(1, 3, 5, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(2, 3, 5, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 3, 5, TOP, LIGHT, DARK); + assertIndicatorsEquals(4, 3, 5, TOP, DARK, LIGHT); + } + + @Test + public void verify3Indicators6Pages() { + assertIndicatorsEquals(0, 3, 6, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(1, 3, 6, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(2, 3, 6, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 3, 6, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(4, 3, 6, TOP, LIGHT, DARK); + assertIndicatorsEquals(5, 3, 6, TOP, DARK, LIGHT); + } + + @Test + public void verify3Indicators7Pages() { + assertIndicatorsEquals(0, 3, 7, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(1, 3, 7, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(2, 3, 7, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 3, 7, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(4, 3, 7, TOP, LIGHT, BOTTOM); + assertIndicatorsEquals(5, 3, 7, TOP, LIGHT, DARK); + assertIndicatorsEquals(6, 3, 7, TOP, DARK, LIGHT); + } + + // + // 4 Indicators + // + + @Test + public void verify4Indicators0Pages() { + assertIndicatorsEquals(0, 4, 0, GONE, GONE, GONE, GONE); + } + + @Test + public void verify4Indicators1Page() { + assertIndicatorsEquals(0, 4, 1, GONE, GONE, GONE, GONE); + } + + @Test + public void verify4Indicators2Pages() { + assertIndicatorsEquals(0, 4, 2, LIGHT, DARK, GONE, GONE); + assertIndicatorsEquals(1, 4, 2, DARK, LIGHT, GONE, GONE); + } + + @Test + public void verify4Indicators3Pages() { + assertIndicatorsEquals(0, 4, 3, LIGHT, DARK, DARK, GONE); + assertIndicatorsEquals(1, 4, 3, DARK, LIGHT, DARK, GONE); + assertIndicatorsEquals(2, 4, 3, DARK, DARK, LIGHT, GONE); + } + + @Test + public void verify4Indicators4Pages() { + assertIndicatorsEquals(0, 4, 4, LIGHT, DARK, DARK, DARK); + assertIndicatorsEquals(1, 4, 4, DARK, LIGHT, DARK, DARK); + assertIndicatorsEquals(2, 4, 4, DARK, DARK, LIGHT, DARK); + assertIndicatorsEquals(3, 4, 4, DARK, DARK, DARK, LIGHT); + } + + @Test + public void verify4Indicators5Pages() { + assertIndicatorsEquals(0, 4, 5, LIGHT, DARK, DARK, BOTTOM); + assertIndicatorsEquals(1, 4, 5, DARK, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(2, 4, 5, DARK, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 4, 5, TOP, DARK, LIGHT, DARK); + assertIndicatorsEquals(4, 4, 5, TOP, DARK, DARK, LIGHT); + } + + @Test + public void verify4Indicators6Pages() { + assertIndicatorsEquals(0, 4, 6, LIGHT, DARK, DARK, BOTTOM); + assertIndicatorsEquals(1, 4, 6, DARK, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(2, 4, 6, DARK, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 4, 6, TOP, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(4, 4, 6, TOP, DARK, LIGHT, DARK); + assertIndicatorsEquals(5, 4, 6, TOP, DARK, DARK, LIGHT); + } + + @Test + public void verify4Indicators7Pages() { + assertIndicatorsEquals(0, 4, 7, LIGHT, DARK, DARK, BOTTOM); + assertIndicatorsEquals(1, 4, 7, DARK, LIGHT, DARK, BOTTOM); + assertIndicatorsEquals(2, 4, 7, DARK, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(3, 4, 7, TOP, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(4, 4, 7, TOP, DARK, LIGHT, BOTTOM); + assertIndicatorsEquals(5, 4, 7, TOP, DARK, LIGHT, DARK); + assertIndicatorsEquals(6, 4, 7, TOP, DARK, DARK, LIGHT); + } + + @Test + public void showTimerSetupView_fromIntent() { + setUpSingleTimer(); + + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + + final Intent intent = TimerFragment.createTimerSetupIntent(fragment.getContext()); + rule.getActivity().setIntent(intent); + restartFragment(); + + assertEquals(View.GONE, timersView.getVisibility()); + assertEquals(View.VISIBLE, timerSetupView.getVisibility()); + } + + @Test + public void showTimerSetupView_usesLabel_fromIntent() { + setUpSingleTimer(); + + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + + final Intent intent = TimerFragment.createTimerSetupIntent(fragment.getContext()); + rule.getActivity().setIntent(intent); + restartFragment(); + + assertEquals(View.GONE, timersView.getVisibility()); + assertEquals(View.VISIBLE, timerSetupView.getVisibility()); + clickView(timerSetupView.findViewById(R.id.timer_setup_digit_3)); + + clickFab(); + } + + @Test + public void showTimer_fromIntent() { + setUpTwoTimers(); + + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + assertEquals(0, viewPager.getCurrentItem()); + + final Intent intent = + new Intent(ApplicationProvider.getApplicationContext(), TimerService.class) + .setAction(TimerService.ACTION_SHOW_TIMER) + .putExtra(TimerService.EXTRA_TIMER_ID, 0); + rule.getActivity().setIntent(intent); + restartFragment(); + + assertEquals(View.VISIBLE, timersView.getVisibility()); + assertEquals(View.GONE, timerSetupView.getVisibility()); + assertEquals(1, viewPager.getCurrentItem()); + } + + private void assertIndicatorsEquals( + int page, int indicatorCount, int pageCount, int... expected) { + int[] actual = TimerFragment.computePageIndicatorStates(page, indicatorCount, pageCount); + if (!Arrays.equals(expected, actual)) { + final String expectedString = Arrays.toString(expected); + final String actualString = Arrays.toString(actual); + fail(String.format("Expected %s, found %s", expectedString, actualString)); + } + } + + private void assertStateEquals(Timer.State expectedState, int index) { + Runnable timerRunnable = () -> { + final Timer.State actualState = + DataModel.getDataModel().getTimers().get(index).getState(); + assertSame(expectedState, actualState); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(timerRunnable); + } + + private void assertAdapter(int count) { + Runnable assertRunnable = () -> { + assertEquals(count, adapter.getCount()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(assertRunnable); + } + + private void restartFragment() { + Runnable onStartRunnable = () -> { + fragment.onStart(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(onStartRunnable); + } + + private void setCurrentItem(int position) { + Runnable setCurrentItemRunnable = () -> { + viewPager.setCurrentItem(position); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(setCurrentItemRunnable); + } + + private void clickView(View view) { + Runnable clickRunnable = () -> { + view.performClick(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(clickRunnable); + } + + private void clickFab() { + Runnable clickRunnable = () -> { + fab.performClick(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(clickRunnable); + } +} diff --git a/tests/src/com/android/deskclock/timer/TimerItemFragmentTest.java b/tests/src/com/android/deskclock/timer/TimerItemFragmentTest.java new file mode 100644 index 000000000..7927d66ae --- /dev/null +++ b/tests/src/com/android/deskclock/timer/TimerItemFragmentTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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.deskclock.timer; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; +import androidx.viewpager.widget.ViewPager; + +import com.android.deskclock.DeskClock; +import com.android.deskclock.R; +import com.android.deskclock.data.DataModel; +import com.android.deskclock.data.Timer; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Exercise the user interface that shows current timers. + */ +@RunWith(AndroidJUnit4ClassRunner.class) +public class TimerItemFragmentTest { + + @Rule + public ActivityTestRule<DeskClock> rule = new ActivityTestRule<>(DeskClock.class, true); + + @Test + public void ensureTimerIsHeldSuccessfully_whenOneTimerIsRunning() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final TimerFragment timerFragment = new TimerFragment(); + rule.getActivity().getFragmentManager() + .beginTransaction().add(timerFragment, null).commit(); + Runnable selectTabRunnable = () -> { + timerFragment.selectTab(); + Timer timer = DataModel.getDataModel().addTimer(5000L, "", false); + + // Get the view held by the TimerFragment + final View view = timerFragment.getView(); + assertNotNull(view); + + // Get the TimerPagerAdapter associated with this view + ViewPager viewPager = (ViewPager) view.findViewById(R.id.vertical_view_pager); + TimerPagerAdapter adapter = (TimerPagerAdapter) viewPager.getAdapter(); + ViewGroup viewGroup = view.findViewById(R.id.timer_view); + + // Retrieve the TimerItemFragment from the adapter + TimerItemFragment timerItemFragment = + (TimerItemFragment) adapter.instantiateItem(viewGroup, 0); + + // Assert that the correct timer is set + assertEquals(timerItemFragment.getTimer(), timer); + DataModel.getDataModel().removeTimer(timer); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(selectTabRunnable); + } +} diff --git a/tests/src/com/android/deskclock/timer/TimerServiceTest.java b/tests/src/com/android/deskclock/timer/TimerServiceTest.java new file mode 100644 index 000000000..985fd0d6b --- /dev/null +++ b/tests/src/com/android/deskclock/timer/TimerServiceTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 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.deskclock.timer; + +import android.content.ComponentName; +import android.content.Intent; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.deskclock.data.DataModel; +import com.android.deskclock.data.Timer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +import static android.app.Service.START_NOT_STICKY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class TimerServiceTest { + + private TimerService timerService; + private DataModel dataModel; + + @Before + public void setUp() { + dataModel = DataModel.getDataModel(); + timerService = new TimerService(); + timerService.onCreate(); + } + + @After + public void tearDown() { + clearTimers(); + dataModel = null; + timerService = null; + } + + private void clearTimers() { + Runnable clearTimersRunnable = () -> { + final List<Timer> timers = new ArrayList<>(DataModel.getDataModel().getTimers()); + for (Timer timer : timers) { + DataModel.getDataModel().removeTimer(timer); + } + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(clearTimersRunnable); + } + + @Test + public void verifyIntentsHonored_whileTimersFire() { + Runnable testRunnable = () -> { + Timer timer1 = dataModel.addTimer(60000L, null, false); + Timer timer2 = dataModel.addTimer(60000L, null, false); + dataModel.startTimer(timer1); + dataModel.startTimer(timer2); + timer1 = dataModel.getTimer(timer1.getId()); + timer2 = dataModel.getTimer(timer2.getId()); + + // Expire the first timer. + dataModel.expireTimer(null, timer1); + + // Have TimerService honor the Intent. + assertEquals(START_NOT_STICKY, + timerService.onStartCommand(getTimerServiceIntent(), 0, 0)); + + // Expire the second timer. + dataModel.expireTimer(null, timer2); + + // Have TimerService honor the Intent which updates the firing timers. + assertEquals(START_NOT_STICKY, + timerService.onStartCommand(getTimerServiceIntent(), 0, 1)); + + // Reset timer 1. + dataModel.resetTimer(dataModel.getTimer(timer1.getId())); + + // Have TimerService honor the Intent which updates the firing timers. + assertEquals(START_NOT_STICKY, + timerService.onStartCommand(getTimerServiceIntent(), 0, 2)); + + // Remove timer 2. + dataModel.removeTimer(dataModel.getTimer(timer2.getId())); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(testRunnable); + } + + @Test + public void verifyIntentsHonored_ifNoTimersAreExpired() { + Runnable testRunnable = () -> { + Timer timer = dataModel.addTimer(60000L, null, false); + dataModel.startTimer(timer); + timer = dataModel.getTimer(timer.getId()); + + // Expire the timer. + dataModel.expireTimer(null, timer); + + final Intent timerServiceIntent = getTimerServiceIntent(); + + // Reset the timer before TimerService starts. + dataModel.resetTimer(dataModel.getTimer(timer.getId())); + + // Have TimerService honor the Intent. + assertEquals(START_NOT_STICKY, timerService.onStartCommand(timerServiceIntent, 0, 0)); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(testRunnable); + } + + private Intent getTimerServiceIntent() { + Intent serviceIntent = new Intent(ApplicationProvider.getApplicationContext(), + TimerService.class); + + final ComponentName component = serviceIntent.getComponent(); + assertNotNull(component); + assertEquals(TimerService.class.getName(), component.getClassName()); + + return serviceIntent; + } +} diff --git a/tests/src/com/android/deskclock/timer/TimerSetupViewTest.java b/tests/src/com/android/deskclock/timer/TimerSetupViewTest.java new file mode 100644 index 000000000..ad135f004 --- /dev/null +++ b/tests/src/com/android/deskclock/timer/TimerSetupViewTest.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2020 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.deskclock.timer; + +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import com.android.deskclock.DeskClock; +import com.android.deskclock.R; +import com.android.deskclock.data.DataModel; +import com.android.deskclock.data.Timer; +import com.android.deskclock.widget.MockFabContainer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Exercise the user interface that collects new timer lengths. + */ +@RunWith(AndroidJUnit4ClassRunner.class) +public class TimerSetupViewTest { + + private MockFabContainer fabContainer; + private TimerSetupView timerSetupView; + + private TextView timeView; + private View deleteView; + + private Locale defaultLocale; + + @Rule + public ActivityTestRule<DeskClock> rule = new ActivityTestRule<>(DeskClock.class, true); + + @Before + public void setUp() { + defaultLocale = Locale.getDefault(); + Locale.setDefault(new Locale("en", "US")); + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final TimerFragment fragment = new TimerFragment(); + rule.getActivity().getFragmentManager().beginTransaction().add(fragment, null).commit(); + Runnable selectTabRunnable = () -> { + fragment.selectTab(); + fabContainer = new MockFabContainer(fragment, context); + fragment.setFabContainer(fabContainer); + + // Fetch the child views the tests will manipulate. + final View view = fragment.getView(); + assertNotNull(view); + + timerSetupView = view.findViewById(R.id.timer_setup); + assertNotNull(timerSetupView); + assertEquals(VISIBLE, timerSetupView.getVisibility()); + + timeView = timerSetupView.findViewById(R.id.timer_setup_time); + timeView.setActivated(true); + deleteView = timerSetupView.findViewById(R.id.timer_setup_delete); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(selectTabRunnable); + } + + @After + public void tearDown() { + fabContainer = null; + timerSetupView = null; + + timeView = null; + deleteView = null; + + Locale.setDefault(defaultLocale); + } + + private void validateDefaultState() { + assertIsReset(); + } + + @Test + public void validateDefaultState_TimersExist() { + Runnable addTimerRunnable = () -> { + Timer timer = DataModel.getDataModel().addTimer(5000L, "", false); + validateDefaultState(); + DataModel.getDataModel().removeTimer(timer); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + } + + @Test + public void validateDefaultState_NoTimersExist() { + Runnable runnable = () -> { + validateDefaultState(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + private void type0InDefaultState() { + performClick(R.id.timer_setup_digit_0); + assertIsReset(); + } + + @Test + public void type0InDefaultState_TimersExist() { + Runnable addTimerRunnable = () -> { + Timer timer = DataModel.getDataModel().addTimer(5000L, "", false); + type0InDefaultState(); + DataModel.getDataModel().removeTimer(timer); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + } + + @Test + public void type0InDefaultState_NoTimersExist() { + Runnable runnable = () -> { + type0InDefaultState(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + private void fillDisplayThenDeleteAll() { + assertIsReset(); + // Fill the display. + performClick(R.id.timer_setup_digit_1); + assertHasValue(0, 0, 1); + performClick(R.id.timer_setup_digit_2); + assertHasValue(0, 0, 12); + performClick(R.id.timer_setup_digit_3); + assertHasValue(0, 1, 23); + performClick(R.id.timer_setup_digit_4); + assertHasValue(0, 12, 34); + performClick(R.id.timer_setup_digit_5); + assertHasValue(1, 23, 45); + performClick(R.id.timer_setup_digit_6); + assertHasValue(12, 34, 56); + + // Typing another character is ignored. + performClick(R.id.timer_setup_digit_7); + assertHasValue(12, 34, 56); + performClick(R.id.timer_setup_digit_8); + assertHasValue(12, 34, 56); + + // Delete everything in the display. + performClick(R.id.timer_setup_delete); + assertHasValue(1, 23, 45); + performClick(R.id.timer_setup_delete); + assertHasValue(0, 12, 34); + performClick(R.id.timer_setup_delete); + assertHasValue(0, 1, 23); + performClick(R.id.timer_setup_delete); + assertHasValue(0, 0, 12); + performClick(R.id.timer_setup_delete); + assertHasValue(0, 0, 1); + performClick(R.id.timer_setup_delete); + assertIsReset(); + } + + @Test + public void fillDisplayThenDeleteAll_TimersExist() { + Runnable addTimerRunnable = () -> { + Timer timer = DataModel.getDataModel().addTimer(5000L, "", false); + fillDisplayThenDeleteAll(); + DataModel.getDataModel().removeTimer(timer); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + } + + @Test + public void fillDisplayThenDeleteAll_NoTimersExist() { + Runnable runnable = () -> { + fillDisplayThenDeleteAll(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + private void fillDisplayWith9s() { + performClick(R.id.timer_setup_digit_9); + performClick(R.id.timer_setup_digit_9); + performClick(R.id.timer_setup_digit_9); + performClick(R.id.timer_setup_digit_9); + performClick(R.id.timer_setup_digit_9); + performClick(R.id.timer_setup_digit_9); + assertHasValue(99, 99, 99); + } + + @Test + public void fillDisplayWith9s_TimersExist() { + Runnable addTimerRunnable = () -> { + Timer timer = DataModel.getDataModel().addTimer(5000L, "", false); + fillDisplayWith9s(); + DataModel.getDataModel().removeTimer(timer); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(addTimerRunnable); + } + + @Test + public void fillDisplayWith9s_NoTimersExist() { + Runnable runnable = () -> { + fillDisplayWith9s(); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable); + } + + private void assertIsReset() { + assertStateEquals(0, 0, 0); + assertFalse(timerSetupView.hasValidInput()); + assertEquals(0, timerSetupView.getTimeInMillis()); + + assertTrue(TextUtils.equals("00h 00m 00s", timeView.getText())); + assertTrue(TextUtils.equals("0 hours, 0 minutes, 0 seconds", + timeView.getContentDescription())); + + assertFalse(deleteView.isEnabled()); + assertTrue(TextUtils.equals("Delete", deleteView.getContentDescription())); + + final View fab = fabContainer.getFab(); + final TextView leftButton = fabContainer.getLeftButton(); + final TextView rightButton = fabContainer.getRightButton(); + + if (DataModel.getDataModel().getTimers().isEmpty()) { + assertEquals(INVISIBLE, leftButton.getVisibility()); + } else { + assertEquals(VISIBLE, leftButton.getVisibility()); + assertTrue(TextUtils.equals("Cancel", leftButton.getText())); + } + + assertNull(fab.getContentDescription()); + assertEquals(INVISIBLE, fab.getVisibility()); + assertEquals(INVISIBLE, rightButton.getVisibility()); + } + + private void assertHasValue(int hours, int minutes, int seconds) { + final long time = + hours * HOUR_IN_MILLIS + minutes * MINUTE_IN_MILLIS + seconds * SECOND_IN_MILLIS; + assertStateEquals(hours, minutes, seconds); + assertTrue(timerSetupView.hasValidInput()); + assertEquals(time, timerSetupView.getTimeInMillis()); + + final String timeString = + String.format(Locale.US, "%02dh %02dm %02ds", hours, minutes, seconds); + assertTrue(TextUtils.equals(timeString, timeView.getText())); + + assertTrue(deleteView.isEnabled()); + assertTrue(TextUtils.equals("Delete " + seconds % 10, deleteView.getContentDescription())); + + final View fab = fabContainer.getFab(); + final TextView leftButton = fabContainer.getLeftButton(); + final TextView rightButton = fabContainer.getRightButton(); + + if (DataModel.getDataModel().getTimers().isEmpty()) { + assertEquals(INVISIBLE, leftButton.getVisibility()); + } else { + assertEquals(VISIBLE, leftButton.getVisibility()); + assertTrue(TextUtils.equals("Cancel", leftButton.getText())); + } + + assertEquals(VISIBLE, fab.getVisibility()); + assertTrue(TextUtils.equals("Start", fab.getContentDescription())); + assertEquals(INVISIBLE, rightButton.getVisibility()); + } + + private void assertStateEquals(int hours, int minutes, int seconds) { + final int[] expected = { + seconds % 10, seconds / 10, minutes % 10, minutes / 10, hours % 10, hours / 10 + }; + final int[] actual = (int[]) timerSetupView.getState(); + assertArrayEquals(expected, actual); + } + + private void performClick(@IdRes int id) { + final View view = timerSetupView.findViewById(id); + assertNotNull(view); + assertTrue(view.performClick()); + } +} diff --git a/tests/src/com/android/deskclock/uidata/FormattedStringModelTest.java b/tests/src/com/android/deskclock/uidata/FormattedStringModelTest.java new file mode 100644 index 000000000..989354536 --- /dev/null +++ b/tests/src/com/android/deskclock/uidata/FormattedStringModelTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 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.deskclock.uidata; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class FormattedStringModelTest { + + private FormattedStringModel model; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + model = new FormattedStringModel(context); + } + + @After + public void tearDown() { + model = null; + } + + @Test + public void positiveFormattedNumberWithNoPadding() { + assertEquals("0", model.getFormattedNumber(0)); + assertEquals("9", model.getFormattedNumber(9)); + assertEquals("10", model.getFormattedNumber(10)); + assertEquals("99", model.getFormattedNumber(99)); + assertEquals("100", model.getFormattedNumber(100)); + } + + @Test + public void positiveFormattedNumber() { + assertEquals("0", model.getFormattedNumber(false, 0, 1)); + assertEquals("00", model.getFormattedNumber(false, 0, 2)); + assertEquals("000", model.getFormattedNumber(false, 0, 3)); + + assertEquals("9", model.getFormattedNumber(false, 9, 1)); + assertEquals("09", model.getFormattedNumber(false, 9, 2)); + assertEquals("009", model.getFormattedNumber(false, 9, 3)); + + assertEquals("90", model.getFormattedNumber(false, 90, 2)); + assertEquals("090", model.getFormattedNumber(false, 90, 3)); + + assertEquals("999", model.getFormattedNumber(false, 999, 3)); + } + + @Test + public void negativeFormattedNumber() { + assertEquals("−0", model.getFormattedNumber(true, 0, 1)); + assertEquals("−00", model.getFormattedNumber(true, 0, 2)); + assertEquals("−000", model.getFormattedNumber(true, 0, 3)); + + assertEquals("−9", model.getFormattedNumber(true, 9, 1)); + assertEquals("−09", model.getFormattedNumber(true, 9, 2)); + assertEquals("−009", model.getFormattedNumber(true, 9, 3)); + + assertEquals("−90", model.getFormattedNumber(true, 90, 2)); + assertEquals("−090", model.getFormattedNumber(true, 90, 3)); + + assertEquals("−999", model.getFormattedNumber(true, 999, 3)); + } +} diff --git a/tests/src/com/android/deskclock/uidata/PeriodicCallbackModelTest.java b/tests/src/com/android/deskclock/uidata/PeriodicCallbackModelTest.java new file mode 100644 index 000000000..3df74041d --- /dev/null +++ b/tests/src/com/android/deskclock/uidata/PeriodicCallbackModelTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 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.deskclock.uidata; + +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Calendar; + +import static com.android.deskclock.uidata.PeriodicCallbackModel.Period.HOUR; +import static com.android.deskclock.uidata.PeriodicCallbackModel.Period.MIDNIGHT; +import static com.android.deskclock.uidata.PeriodicCallbackModel.Period.MINUTE; +import static com.android.deskclock.uidata.PeriodicCallbackModel.Period.QUARTER_HOUR; + +import static java.util.Calendar.MILLISECOND; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class PeriodicCallbackModelTest { + + @Test + public void getMinuteDelay() { + assertEquals(1, PeriodicCallbackModel.getDelay(56999, MINUTE, -3000)); + assertEquals(60000, PeriodicCallbackModel.getDelay(57000, MINUTE, -3000)); + assertEquals(59999, PeriodicCallbackModel.getDelay(57001, MINUTE, -3000)); + + assertEquals(1, PeriodicCallbackModel.getDelay(59999, MINUTE, 0)); + assertEquals(60000, PeriodicCallbackModel.getDelay(60000, MINUTE, 0)); + assertEquals(59999, PeriodicCallbackModel.getDelay(60001, MINUTE, 0)); + + assertEquals(3001, PeriodicCallbackModel.getDelay(59999, MINUTE, 3000)); + assertEquals(3000, PeriodicCallbackModel.getDelay(60000, MINUTE, 3000)); + assertEquals(1, PeriodicCallbackModel.getDelay(62999, MINUTE, 3000)); + assertEquals(60000, PeriodicCallbackModel.getDelay(63000, MINUTE, 3000)); + assertEquals(59999, PeriodicCallbackModel.getDelay(63001, MINUTE, 3000)); + } + + @Test + public void getQuarterHourDelay() { + assertEquals(1, PeriodicCallbackModel.getDelay(896999, QUARTER_HOUR, -3000)); + assertEquals(900000, PeriodicCallbackModel.getDelay(897000, QUARTER_HOUR, -3000)); + assertEquals(899999, PeriodicCallbackModel.getDelay(897001, QUARTER_HOUR, -3000)); + + assertEquals(1, PeriodicCallbackModel.getDelay(899999, QUARTER_HOUR, 0)); + assertEquals(900000, PeriodicCallbackModel.getDelay(900000, QUARTER_HOUR, 0)); + assertEquals(899999, PeriodicCallbackModel.getDelay(900001, QUARTER_HOUR, 0)); + + assertEquals(3001, PeriodicCallbackModel.getDelay(899999, QUARTER_HOUR, 3000)); + assertEquals(3000, PeriodicCallbackModel.getDelay(900000, QUARTER_HOUR, 3000)); + assertEquals(1, PeriodicCallbackModel.getDelay(902999, QUARTER_HOUR, 3000)); + assertEquals(900000, PeriodicCallbackModel.getDelay(903000, QUARTER_HOUR, 3000)); + assertEquals(899999, PeriodicCallbackModel.getDelay(903001, QUARTER_HOUR, 3000)); + } + + @Test + public void getHourDelay() { + assertEquals(1, PeriodicCallbackModel.getDelay(3596999, HOUR, -3000)); + assertEquals(3600000, PeriodicCallbackModel.getDelay(3597000, HOUR, -3000)); + assertEquals(3599999, PeriodicCallbackModel.getDelay(3597001, HOUR, -3000)); + + assertEquals(1, PeriodicCallbackModel.getDelay(3599999, HOUR, 0)); + assertEquals(3600000, PeriodicCallbackModel.getDelay(3600000, HOUR, 0)); + assertEquals(3599999, PeriodicCallbackModel.getDelay(3600001, HOUR, 0)); + + assertEquals(3001, PeriodicCallbackModel.getDelay(3599999, HOUR, 3000)); + assertEquals(3000, PeriodicCallbackModel.getDelay(3600000, HOUR, 3000)); + assertEquals(1, PeriodicCallbackModel.getDelay(3602999, HOUR, 3000)); + assertEquals(3600000, PeriodicCallbackModel.getDelay(3603000, HOUR, 3000)); + assertEquals(3599999, PeriodicCallbackModel.getDelay(3603001, HOUR, 3000)); + } + + @Test + public void getMidnightDelay() { + final Calendar c = Calendar.getInstance(); + c.set(2016, 0, 20, 0, 0, 0); + c.set(MILLISECOND, 0); + final long now = c.getTimeInMillis(); + + assertEquals(1, PeriodicCallbackModel.getDelay(now - 3001, MIDNIGHT, -3000)); + assertEquals(86400000, PeriodicCallbackModel.getDelay(now - 3000, MIDNIGHT, -3000)); + assertEquals(86399999, PeriodicCallbackModel.getDelay(now - 2999, MIDNIGHT, -3000)); + + assertEquals(1, PeriodicCallbackModel.getDelay(now - 1, MIDNIGHT, 0)); + assertEquals(86400000, PeriodicCallbackModel.getDelay(now, MIDNIGHT, 0)); + assertEquals(86399999, PeriodicCallbackModel.getDelay(now + 1, MIDNIGHT, 0)); + + assertEquals(3001, PeriodicCallbackModel.getDelay(now - 1, MIDNIGHT, 3000)); + assertEquals(3000, PeriodicCallbackModel.getDelay(now, MIDNIGHT, 3000)); + assertEquals(1, PeriodicCallbackModel.getDelay(now + 2999, MIDNIGHT, 3000)); + assertEquals(86400000, PeriodicCallbackModel.getDelay(now + 3000, MIDNIGHT, 3000)); + assertEquals(86399999, PeriodicCallbackModel.getDelay(now + 3001, MIDNIGHT, 3000)); + } +} diff --git a/tests/src/com/android/deskclock/uidata/TabModelTest.java b/tests/src/com/android/deskclock/uidata/TabModelTest.java new file mode 100644 index 000000000..dbe934498 --- /dev/null +++ b/tests/src/com/android/deskclock/uidata/TabModelTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 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.deskclock.uidata; + +import android.content.Context; +import android.preference.PreferenceManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +import static com.android.deskclock.uidata.UiDataModel.Tab.ALARMS; +import static com.android.deskclock.uidata.UiDataModel.Tab.CLOCKS; +import static com.android.deskclock.uidata.UiDataModel.Tab.STOPWATCH; +import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS; + +import static org.junit.Assert.assertSame; + +@RunWith(AndroidJUnit4ClassRunner.class) +public class TabModelTest { + + private Locale defaultLocale; + private TabModel tabModel; + + @Test + public void ltrTabLayoutIndex() { + setUpTabModel(new Locale("en", "US")); + assertSame(ALARMS, tabModel.getTabAt(0)); + assertSame(CLOCKS, tabModel.getTabAt(1)); + assertSame(TIMERS, tabModel.getTabAt(2)); + assertSame(STOPWATCH, tabModel.getTabAt(3)); + Locale.setDefault(defaultLocale); + } + + @Test + public void rtlTabLayoutIndex() { + setUpTabModel(new Locale("ar", "EG")); + assertSame(STOPWATCH, tabModel.getTabAt(0)); + assertSame(TIMERS, tabModel.getTabAt(1)); + assertSame(CLOCKS, tabModel.getTabAt(2)); + assertSame(ALARMS, tabModel.getTabAt(3)); + Locale.setDefault(defaultLocale); + } + + private void setUpTabModel(Locale testLocale) { + defaultLocale = Locale.getDefault(); + Locale.setDefault(testLocale); + final Context context = ApplicationProvider.getApplicationContext(); + tabModel = new TabModel(PreferenceManager.getDefaultSharedPreferences(context)); + } +} diff --git a/tests/src/com/android/deskclock/widget/MockFabContainer.java b/tests/src/com/android/deskclock/widget/MockFabContainer.java new file mode 100644 index 000000000..bdfbf7dd4 --- /dev/null +++ b/tests/src/com/android/deskclock/widget/MockFabContainer.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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.deskclock.widget; + +import android.content.Context; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; + +import com.android.deskclock.DeskClockFragment; +import com.android.deskclock.FabContainer; + +/** + * DeskClock is the normal container for the fab and its left and right buttons. In order to test + * each tab in isolation, tests avoid inflating all of DeskClock and instead set this mock fab + * container into the DeskClockFragment under test. It mimics the behavior of a fab container, + * albeit without animation, so fragment tests can verify the state of the fab any time they like. + */ +public final class MockFabContainer implements FabContainer { + + private final DeskClockFragment deskClockFragment; + + private ImageView fab; + private Button leftButton; + private Button rightButton; + + public MockFabContainer(DeskClockFragment fragment, Context context) { + deskClockFragment = fragment; + fab = new ImageView(context); + leftButton = new Button(context); + rightButton = new Button(context); + + updateFab(FabContainer.FAB_AND_BUTTONS_IMMEDIATE); + + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + deskClockFragment.onFabClick(fab); + } + }); + leftButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + deskClockFragment.onLeftButtonClick(leftButton); + } + }); + rightButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + deskClockFragment.onRightButtonClick(rightButton); + } + }); + } + + @Override + public void updateFab(@UpdateFabFlag int updateType) { + if ((updateType & FabContainer.FAB_ANIMATION_MASK) != 0) { + deskClockFragment.onUpdateFab(fab); + } + if ((updateType & FabContainer.FAB_REQUEST_FOCUS_MASK) != 0) { + fab.requestFocus(); + } + if ((updateType & FabContainer.BUTTONS_ANIMATION_MASK) != 0) { + deskClockFragment.onUpdateFabButtons(leftButton, rightButton); + } + if ((updateType & FabContainer.BUTTONS_DISABLE_MASK) != 0) { + leftButton.setClickable(false); + rightButton.setClickable(false); + } + } + + public ImageView getFab() { + return fab; + } + + public Button getLeftButton() { + return leftButton; + } + + public Button getRightButton() { + return rightButton; + } +} diff --git a/tests/src/com/android/deskclock/worldclock/CitySelectionActivityTest.java b/tests/src/com/android/deskclock/worldclock/CitySelectionActivityTest.java new file mode 100644 index 000000000..46e003022 --- /dev/null +++ b/tests/src/com/android/deskclock/worldclock/CitySelectionActivityTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 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.deskclock.worldclock; + +import android.widget.ListAdapter; +import android.widget.ListView; + +import androidx.appcompat.widget.SearchView; +import androidx.test.InstrumentationRegistry; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; +import androidx.test.rule.ActivityTestRule; + +import com.android.deskclock.R; +import com.android.deskclock.data.City; +import com.android.deskclock.data.DataModel; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Exercise the user interface that adjusts the selected cities. + */ +@RunWith(AndroidJUnit4ClassRunner.class) +public class CitySelectionActivityTest { + + private CitySelectionActivity activity; + private ListView cities; + private ListAdapter citiesAdapter; + private SearchView searchView; + private Locale defaultLocale; + + @Rule + public ActivityTestRule<CitySelectionActivity> rule = + new ActivityTestRule<>(CitySelectionActivity.class, true); + + @Before + public void setUp() { + defaultLocale = Locale.getDefault(); + Locale.setDefault(new Locale("en", "US")); + Runnable setUpRunnable = () -> { + final City city = DataModel.getDataModel().getAllCities().get(0); + DataModel.getDataModel().setSelectedCities(Collections.singletonList(city)); + activity = rule.getActivity(); + cities = activity.findViewById(R.id.cities_list); + citiesAdapter = (ListAdapter) cities.getAdapter(); + searchView = activity.findViewById(R.id.menu_item_search); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(setUpRunnable); + } + + @After + public void tearDown() { + activity = null; + cities = null; + searchView = null; + Locale.setDefault(defaultLocale); + } + + @Test + public void validateDefaultState() { + Runnable testRunable = () -> { + assertNotNull(searchView); + assertNotNull(cities); + assertNotNull(citiesAdapter); + assertEquals(340, citiesAdapter.getCount()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(testRunable); + } + + @Test + public void searchCities() { + Runnable testRunable = () -> { + // Search for cities starting with Z. + searchView.setQuery("Z", true); + assertEquals(2, citiesAdapter.getCount()); + assertItemContent(0, "Zagreb"); + assertItemContent(1, "Zurich"); + + // Clear the filter query. + searchView.setQuery("", true); + assertEquals(340, citiesAdapter.getCount()); + }; + InstrumentationRegistry.getInstrumentation().runOnMainSync(testRunable); + } + + private void assertItemContent(int index, String cityName) { + final City city = (City) citiesAdapter.getItem(index); + assertEquals(cityName, city.getName()); + } +} |