Refine the SchedulesProvider

Bug: 148995400
Test: make RunSettingsLibRoboTests -j56
Change-Id: I84dfa571eefffa74f49ed861fba7a49d1b50c659
diff --git a/packages/SettingsLib/SchedulesProvider/res/values/config.xml b/packages/SettingsLib/SchedulesProvider/res/values/config.xml
new file mode 100644
index 0000000..48f3e3e
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/res/values/config.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources>
+    <!-- Package name for the caller of the Schedules provider. -->
+    <string name="config_schedules_provider_caller_package" translatable="false">com.android.settings</string>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
index 7d2b8e2..26bcd54 100644
--- a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
@@ -24,9 +24,9 @@
 import androidx.annotation.NonNull;
 
 /**
- * This is a schedule data item. It contains the schedule title text, the summary text which
- * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to
- * launch the editing page of the schedule data when user clicks this item (preference).
+ * Schedule data item containing the schedule title text, the summary text which is displayed on the
+ * summary of the Settings preference and an {@link Intent} which Settings will launch when the
+ * user clicks on the preference.
  */
 public class ScheduleInfo implements Parcelable {
     private static final String TAG = "ScheduleInfo";
@@ -40,7 +40,7 @@
         mIntent = builder.mIntent;
     }
 
-    protected ScheduleInfo(Parcel in) {
+    private ScheduleInfo(Parcel in) {
         mTitle = in.readString();
         mSummary = in.readString();
         mIntent = in.readParcelable(Intent.class.getClassLoader());
@@ -48,8 +48,6 @@
 
     /**
      * Returns the title text.
-     *
-     * @return The title.
      */
     public String getTitle() {
         return mTitle;
@@ -57,15 +55,14 @@
 
     /**
      * Returns the summary text.
-     *
-     * @return The summary.
      */
     public String getSummary() {
         return mSummary;
     }
 
     /**
-     * Returns an {@link Intent}.
+     * Returns an {@link Intent} which Settings will launch when the user clicks on a schedule
+     * preference.
      */
     public Intent getIntent() {
         return mIntent;
@@ -107,19 +104,15 @@
     @NonNull
     @Override
     public String toString() {
-        return "title : " + mTitle + " summary : " + mSummary + (mIntent == null
-                ? " and intent is null." : ".");
+        return "title: " + mTitle + ", summary: " + mSummary + ", intent: " + mIntent;
     }
 
     /**
      * A simple builder for {@link ScheduleInfo}.
      */
     public static class Builder {
-        @NonNull
         private String mTitle;
-        @NonNull
         private String mSummary;
-        @NonNull
         private Intent mIntent;
 
         /**
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
index a423e47..28d5f07 100644
--- a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
@@ -21,19 +21,18 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.stream.Collectors;
 
 /**
- * This provider is a bridge for client apps to provide the schedule data.
- * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of
- * {@link ScheduleInfo}.
+ * A bridge for client apps to provide the schedule data. Client provider needs to implement
+ * {@link #getScheduleInfoList()} returning a list of {@link ScheduleInfo}.
  */
 public abstract class SchedulesProvider extends ContentProvider {
     public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList";
@@ -46,9 +45,8 @@
     }
 
     @Override
-    public final Cursor query(
-            Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
         throw new UnsupportedOperationException("Query operation is not supported currently.");
     }
 
@@ -74,18 +72,24 @@
     }
 
     /**
-     * Return the list of the schedule information.
-     *
-     * @return a list of the {@link ScheduleInfo}.
+     * Returns the list of the schedule information.
      */
     public abstract ArrayList<ScheduleInfo> getScheduleInfoList();
 
     /**
-     * Returns a bundle which contains a list of {@link ScheduleInfo} and data types:
-     * scheduleInfoList : ArrayList<ScheduleInfo>
+     * Returns a bundle which contains a list of {@link ScheduleInfo}s:
+     *
+     * <ul>
+     *   <li>scheduleInfoList: ArrayList<ScheduleInfo>
+     * </ul>
      */
     @Override
     public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+        if (!TextUtils.equals(getCallingPackage(),
+                getContext().getText(R.string.config_schedules_provider_caller_package))) {
+            return null;
+        }
+
         final Bundle bundle = new Bundle();
         if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) {
             final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData(
@@ -98,36 +102,40 @@
     }
 
     /**
-     * To filter the invalid schedule info.
+     * Filters our invalid schedule infos from {@code schedulesInfoList}.
      *
-     * @param scheduleInfoList The list of the {@link ScheduleInfo}.
-     * @return The valid list of the {@link ScheduleInfo}.
+     * @return valid {@link SchedulesInfo}s if {@code schedulesInfoList} is not null. Otherwise,
+     * null.
      */
-    private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+    @Nullable
+    private ArrayList<ScheduleInfo> filterInvalidData(
+            @Nullable ArrayList<ScheduleInfo> scheduleInfoList) {
         if (scheduleInfoList == null) {
             Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data.");
             return null;
         }
         // Dump invalid data in debug mode.
         if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
-            new Thread(() -> {
-                dumpInvalidData(scheduleInfoList);
-            }).start();
+            dumpInvalidData(scheduleInfoList);
         }
-        final List<ScheduleInfo> filteredList = scheduleInfoList
+        return scheduleInfoList
                 .stream()
-                .filter(scheduleInfo -> scheduleInfo.isValid())
-                .collect(Collectors.toList());
-
-        return new ArrayList<>(filteredList);
+                .filter(ScheduleInfo::isValid)
+                .collect(Collectors.toCollection(ArrayList::new));
     }
 
     private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
-        Log.d(TAG, "package : " + getContext().getPackageName()
-                + " provided some scheduling data are invalid.");
-        scheduleInfoList
+        final boolean hasInvalidData = scheduleInfoList
                 .stream()
-                .filter(scheduleInfo -> !scheduleInfo.isValid())
-                .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString()));
+                .anyMatch(scheduleInfo -> !scheduleInfo.isValid());
+
+        if (hasInvalidData) {
+            Log.w(TAG, "package : " + getContext().getPackageName()
+                    + " provided some scheduling data that are invalid.");
+            scheduleInfoList
+                    .stream()
+                    .filter(scheduleInfo -> !scheduleInfo.isValid())
+                    .forEach(scheduleInfo -> Log.w(TAG, scheduleInfo.toString()));
+        }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java
new file mode 100644
index 0000000..5ec89ed
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.settingslib.schedulesprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ScheduleInfoTest {
+    private static final String TEST_TITLE = "Night Light";
+    private static final String TEST_SUMMARY = "Night Light summary";
+    private static final String TEST_EMPTY_SUMMARY = "";
+
+    @Test
+    public void builder_usedValidArguments_isValid() {
+        final Intent intent = createTestIntent();
+        final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+        assertThat(info).isNotNull();
+        assertThat(info.isValid()).isTrue();
+    }
+
+    @Test
+    public void builder_useEmptySummary_isInvalid() {
+        final Intent intent = createTestIntent();
+        final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_EMPTY_SUMMARY, intent);
+
+        assertThat(info).isNotNull();
+        assertThat(info.isValid()).isFalse();
+    }
+
+    @Test
+    public void builder_intentIsNull_isInvalid() {
+        final ScheduleInfo info = new ScheduleInfo.Builder()
+                .setTitle(TEST_TITLE)
+                .setSummary(TEST_SUMMARY)
+                .build();
+
+        assertThat(info).isNotNull();
+        assertThat(info.isValid()).isFalse();
+    }
+
+    @Test
+    public void getTitle_setValidTitle_shouldReturnSameCorrectTitle() {
+        final Intent intent = createTestIntent();
+        final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+        assertThat(info.getTitle()).isEqualTo(TEST_TITLE);
+    }
+
+    @Test
+    public void getSummary_setValidSummary_shouldReturnSameCorrectSummary() {
+        final Intent intent = createTestIntent();
+        final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+        assertThat(info.getSummary()).isEqualTo(TEST_SUMMARY);
+    }
+
+    @Test
+    public void getIntent_setValidIntent_shouldReturnSameCorrectIntent() {
+        final Intent intent = createTestIntent();
+        final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+        assertThat(info.getIntent()).isEqualTo(intent);
+    }
+
+    private static Intent createTestIntent() {
+        return new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+                Intent.CATEGORY_DEFAULT);
+    }
+
+    private static ScheduleInfo createTestScheduleInfo(String title, String summary,
+            Intent intent) {
+        return new ScheduleInfo.Builder()
+                .setTitle(title)
+                .setSummary(summary)
+                .setIntent(intent)
+                .build();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java
new file mode 100644
index 0000000..eb2e8e0
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.settingslib.schedulesprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class SchedulesProviderTest {
+    private static final String INVALID_PACKAGE = "com.android.sunny";
+    private static final String VALID_PACKAGE = "com.android.settings";
+    private static final String INVALID_METHOD = "queryTestData";
+    private TestSchedulesProvider mProvider;
+
+    @Before
+    public void setUp() {
+        mProvider = Robolectric.setupContentProvider(TestSchedulesProvider.class);
+        shadowOf(mProvider).setCallingPackage(VALID_PACKAGE);
+        mProvider.setScheduleInfos(TestSchedulesProvider.createOneValidScheduleInfo());
+    }
+
+    @Test
+    public void call_invalidCallingPkg_returnNull() {
+        shadowOf(mProvider).setCallingPackage(INVALID_PACKAGE);
+
+        final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+                null /* arg */, null /* extras */);
+
+        assertThat(bundle).isNull();
+    }
+
+    @Test
+    public void call_invalidMethod_returnBundleWithoutScheduleInfoData() {
+        final Bundle bundle = mProvider.call(INVALID_METHOD, null /* arg */, null /* extras */);
+
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isFalse();
+    }
+
+    @Test
+    public void call_validMethod_returnScheduleInfoData() {
+        final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+                null /* arg */, null /* extras */);
+
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+        final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+                SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+        assertThat(infos).hasSize(1);
+    }
+
+    @Test
+    public void call_addTwoValidData_returnScheduleInfoData() {
+        mProvider.setScheduleInfos(TestSchedulesProvider.createTwoValidScheduleInfos());
+        final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+                null /* arg */, null /* extras */);
+
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+        final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+                SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+        assertThat(infos).hasSize(2);
+    }
+
+    @Test
+    public void call_addTwoValidDataAndOneInvalidData_returnTwoScheduleInfoData() {
+        mProvider.setScheduleInfos(TestSchedulesProvider.createTwoValidAndOneInvalidScheduleInfo());
+        final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+                null /* arg */, null /* extras */);
+
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+        final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+                SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+        assertThat(infos).hasSize(2);
+    }
+
+    private static class TestSchedulesProvider extends SchedulesProvider {
+        private ArrayList<ScheduleInfo> mScheduleInfos = new ArrayList<>();
+
+        @Override
+        public ArrayList<ScheduleInfo> getScheduleInfoList() {
+            return mScheduleInfos;
+        }
+
+        void setScheduleInfos(ArrayList<ScheduleInfo> scheduleInfos) {
+            mScheduleInfos = scheduleInfos;
+        }
+
+        private static ArrayList<ScheduleInfo> createOneValidScheduleInfo() {
+            final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+            final Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            final ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+                    "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+            scheduleInfos.add(info);
+
+            return scheduleInfos;
+        }
+
+        private static ArrayList<ScheduleInfo> createTwoValidScheduleInfos() {
+            final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+            Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+                    "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+            scheduleInfos.add(info);
+
+            intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            info = new ScheduleInfo.Builder().setTitle("Display").setSummary(
+                    "Display summary").setIntent(intent).build();
+            scheduleInfos.add(info);
+
+            return scheduleInfos;
+        }
+
+        private static ArrayList<ScheduleInfo> createTwoValidAndOneInvalidScheduleInfo() {
+            final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+            Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+                    "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+            scheduleInfos.add(info);
+
+            intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            info = new ScheduleInfo.Builder().setTitle("Display").setSummary(
+                    "Display summary").setIntent(intent).build();
+            scheduleInfos.add(info);
+
+            intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+                    Intent.CATEGORY_DEFAULT);
+            info = new ScheduleInfo.Builder().setTitle("").setSummary("Display summary").setIntent(
+                    intent).build();
+            scheduleInfos.add(info);
+
+            return scheduleInfos;
+        }
+    }
+}