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

Commit 65f3c221 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Refine the SchedulesProvider" into rvc-dev am: e0ffd803 am: d31a1aaa

Change-Id: I431c9b1e58ac3823464bb4f5806f8f7534dc060e
parents 1c069659 d31a1aaa
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
<?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
+7 −14
Original line number Diff line number Diff line
@@ -24,9 +24,9 @@ import android.text.TextUtils;
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 @@ public class ScheduleInfo implements Parcelable {
        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 @@ public class ScheduleInfo implements Parcelable {

    /**
     * Returns the title text.
     *
     * @return The title.
     */
    public String getTitle() {
        return mTitle;
@@ -57,15 +55,14 @@ public class ScheduleInfo implements Parcelable {

    /**
     * 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 @@ public class ScheduleInfo implements Parcelable {
    @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;

        /**
+37 −29
Original line number Diff line number Diff line
@@ -21,19 +21,18 @@ import android.database.Cursor;
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 @@ public abstract class SchedulesProvider extends ContentProvider {
    }

    @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 @@ public abstract class SchedulesProvider extends ContentProvider {
    }

    /**
     * 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 @@ public abstract class SchedulesProvider extends ContentProvider {
    }

    /**
     * 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();
        }
        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.");
        final boolean hasInvalidData = scheduleInfoList
                .stream()
                .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.d(TAG, scheduleInfo.toString()));
                    .forEach(scheduleInfo -> Log.w(TAG, scheduleInfo.toString()));
        }
    }
}
+98 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+166 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}