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

Commit 5ad8ef26 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Added the CardContentProvider"

parents 36681bbf 195f61b2
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -3181,6 +3181,12 @@
        <service android:name=".fuelgauge.batterytip.AnomalyDetectionJobService"
                 android:permission="android.permission.BIND_JOB_SERVICE" />

        <provider
            android:name=".homepage.CardContentProvider"
            android:authorities="com.android.settings.homepage.CardContentProvider"
            android:exported="true"
            android:permission="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA" />

        <!-- This is the longest AndroidManifest.xml ever. -->
    </application>
</manifest>
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.homepage;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Build;
import android.os.StrictMode;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

/**
 * Provider stores and manages user interaction feedback for homepage contextual cards.
 */
public class CardContentProvider extends ContentProvider {

    private static final String TAG = "CardContentProvider";

    public static final String CARD_AUTHORITY = "com.android.settings.homepage.CardContentProvider";

    /** URI matcher for ContentProvider queries. */
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    /** URI matcher type for cards table */
    private static final int MATCH_CARDS = 100;
    /** URI matcher type for card log table */
    private static final int MATCH_CARD_LOG = 200;

    static {
        sUriMatcher.addURI(CARD_AUTHORITY, CardDatabaseHelper.CARD_TABLE, MATCH_CARDS);
    }

    private CardDatabaseHelper mDBHelper;

    @Override
    public boolean onCreate() {
        mDBHelper = CardDatabaseHelper.getInstance(getContext());
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
        try {
            if (Build.IS_DEBUGGABLE) {
                enableStrictMode(true);
            }

            final SQLiteDatabase database = mDBHelper.getWritableDatabase();
            final String table = getTableFromMatch(uri);
            final long ret = database.insert(table, null, values);
            if (ret != -1) {
                getContext().getContentResolver().notifyChange(uri, null);
            } else {
                Log.e(TAG, "The CardContentProvider insertion failed! Plase check SQLiteDatabase's "
                        + "message.");
            }
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
        return uri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
        try {
            if (Build.IS_DEBUGGABLE) {
                enableStrictMode(true);
            }

            final SQLiteDatabase database = mDBHelper.getWritableDatabase();
            final String table = getTableFromMatch(uri);
            final int rowsDeleted = database.delete(table, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);
            return rowsDeleted;
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("getType operation not supported currently.");
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
        try {
            if (Build.IS_DEBUGGABLE) {
                enableStrictMode(true);
            }

            final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
            final String table = getTableFromMatch(uri);
            queryBuilder.setTables(table);
            final SQLiteDatabase database = mDBHelper.getReadableDatabase();
            final Cursor cursor = queryBuilder.query(database,
                    projection, selection, selectionArgs, null, null, sortOrder);

            cursor.setNotificationUri(getContext().getContentResolver(), uri);
            return cursor;
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
        try {
            if (Build.IS_DEBUGGABLE) {
                enableStrictMode(true);
            }

            final SQLiteDatabase database = mDBHelper.getWritableDatabase();
            final String table = getTableFromMatch(uri);
            final int rowsUpdated = database.update(table, values, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);
            return rowsUpdated;
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void enableStrictMode(boolean enabled) {
        StrictMode.setThreadPolicy(enabled
                ? new StrictMode.ThreadPolicy.Builder().detectAll().build()
                : StrictMode.ThreadPolicy.LAX);
    }

    @VisibleForTesting
    String getTableFromMatch(Uri uri) {
        final int match = sUriMatcher.match(uri);
        String table;
        switch (match) {
            case MATCH_CARDS:
                table = CardDatabaseHelper.CARD_TABLE;
                break;
            default:
                throw new IllegalArgumentException("Unknown Uri format: " + uri);
        }
        return table;
    }
}
+190 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.homepage;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.VisibleForTesting;

/**
 * Defines the schema for the Homepage Cards database.
 */
public class CardDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "homepage_cards.db";
    private static final int DATABASE_VERSION = 1;

    public static final String CARD_TABLE = "cards";

    public interface CardColumns {
        /**
         * Primary key. Name of the card.
         */
        String NAME = "name";

        /**
         * Type of the card.
         */
        String TYPE = "type";

        /**
         * Score of the card. Higher numbers have higher priorities.
         */
        String SCORE = "score";

        /**
         * URI of the slice card.
         */
        String SLICE_URI = "slice_uri";

        /**
         * Category of the card. The value is between 0 to 3.
         */
        String CATEGORY = "category";

        /**
         * URI decides the card can be shown.
         */
        String AVAILABILITY_URI = "availability_uri";

        /**
         * Keep the card last display's locale.
         */
        String LOCALIZED_TO_LOCALE = "localized_to_locale";

        /**
         * Package name for all card candidates.
         */
        String PACKAGE_NAME = "package_name";

        /**
         * Application version of the package.
         */
        String APP_VERSION = "app_version";

        /**
         * Title resource name of the package.
         */
        String TITLE_RES_NAME = "title_res_name";

        /**
         * Title of the package to be shown.
         */
        String TITLE_TEXT = "title_text";

        /**
         * Summary resource name of the package.
         */
        String SUMMARY_RES_NAME = "summary_res_name";

        /**
         * Summary of the package to be shown.
         */
        String SUMMARY_TEXT = "summary_text";

        /**
         * Icon resource name of the package.
         */
        String ICON_RES_NAME = "icon_res_name";

        /**
         * Icon resource id of the package.
         */
        String ICON_RES_ID = "icon_res_id";

        /**
         * PendingIntent for for custom view card candidate. Do action when user press card.
         */
        String CARD_ACTION = "card_action";

        /**
         * Expire time of the card. The unit of the value is mini-second.
         */
        String EXPIRE_TIME_MS = "expire_time_ms";
    }

    private static final String CREATE_CARD_TABLE =
            "CREATE TABLE " + CARD_TABLE +
                    "(" +
                    CardColumns.NAME +
                    " TEXT NOT NULL PRIMARY KEY, " +
                    CardColumns.TYPE +
                    " INTEGER NOT NULL, " +
                    CardColumns.SCORE +
                    " DOUBLE NOT NULL, " +
                    CardColumns.SLICE_URI +
                    " TEXT, " +
                    CardColumns.CATEGORY +
                    " INTEGER DEFAULT 0 CHECK (" +
                    CardColumns.CATEGORY +
                    " >= 0 AND " +
                    CardColumns.CATEGORY +
                    " <= 3), " +
                    CardColumns.AVAILABILITY_URI +
                    " TEXT, " +
                    CardColumns.LOCALIZED_TO_LOCALE +
                    " TEXT, " +
                    CardColumns.PACKAGE_NAME +
                    " TEXT NOT NULL, " +
                    CardColumns.APP_VERSION +
                    " TEXT NOT NULL, " +
                    CardColumns.TITLE_RES_NAME +
                    " TEXT, " +
                    CardColumns.TITLE_TEXT +
                    " TEXT, " +
                    CardColumns.SUMMARY_RES_NAME +
                    " TEXT, " +
                    CardColumns.SUMMARY_TEXT +
                    " TEXT, " +
                    CardColumns.ICON_RES_NAME +
                    " TEXT, " +
                    CardColumns.ICON_RES_ID +
                    " INTEGER DEFAULT 0, " +
                    CardColumns.CARD_ACTION +
                    " TEXT, " +
                    CardColumns.EXPIRE_TIME_MS +
                    " INTEGER " +
                    ");";

    public CardDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_CARD_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion < newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + CARD_TABLE);
            onCreate(db);
        }
    }

    @VisibleForTesting
    static CardDatabaseHelper sCardDatabaseHelper;

    public static synchronized CardDatabaseHelper getInstance(Context context) {
        if (sCardDatabaseHelper == null) {
            sCardDatabaseHelper = new CardDatabaseHelper(context.getApplicationContext());
        }
        return sCardDatabaseHelper;
    }
}
+147 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.android.settings.homepage;

import static com.google.common.truth.Truth.assertThat;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

import com.android.settings.testutils.SettingsRobolectricTestRunner;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;

@RunWith(SettingsRobolectricTestRunner.class)
public class CardContentProviderTest {

    private Context mContext;
    private CardContentProvider mProvider;
    private Uri mUri;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        mProvider = Robolectric.setupContentProvider(CardContentProvider.class);
        mUri = new Uri.Builder()
                .scheme(ContentResolver.SCHEME_CONTENT)
                .authority(CardContentProvider.CARD_AUTHORITY)
                .path(CardDatabaseHelper.CARD_TABLE)
                .build();
    }

    @After
    public void cleanUp() {
        CardDatabaseHelper.getInstance(mContext).close();
        CardDatabaseHelper.sCardDatabaseHelper = null;
    }

    @Test
    public void cardData_insert() {
        final int cnt_before_instert = getRowCount();
        mContext.getContentResolver().insert(mUri, insertOneRow());
        final int cnt_after_instert = getRowCount();

        assertThat(cnt_after_instert - cnt_before_instert).isEqualTo(1);
    }

    @Test
    public void cardData_query() {
        mContext.getContentResolver().insert(mUri, insertOneRow());
        final int count = getRowCount();

        assertThat(count).isGreaterThan(0);
    }

    @Test
    public void cardData_delete() {
        final ContentResolver contentResolver = mContext.getContentResolver();
        contentResolver.insert(mUri, insertOneRow());
        final int del_count = contentResolver.delete(mUri, null, null);

        assertThat(del_count).isGreaterThan(0);
    }

    @Test
    public void cardData_update() {
        final ContentResolver contentResolver = mContext.getContentResolver();
        contentResolver.insert(mUri, insertOneRow());

        final double updatingScore= 0.87;
        final ContentValues values = new ContentValues();
        values.put(CardDatabaseHelper.CardColumns.SCORE, updatingScore);
        final String strWhere = CardDatabaseHelper.CardColumns.NAME + "=?";
        final String[] selectionArgs = {"auto_rotate"};
        final int update_count = contentResolver.update(mUri, values, strWhere, selectionArgs);

        assertThat(update_count).isGreaterThan(0);

        final String[] columns = {CardDatabaseHelper.CardColumns.SCORE};
        final Cursor cr = contentResolver.query(mUri, columns, strWhere, selectionArgs, null);
        cr.moveToFirst();
        final double qryScore = cr.getDouble(0);

        cr.close();
        assertThat(qryScore).isEqualTo(updatingScore);
    }

    @Test(expected = UnsupportedOperationException.class)
    public void getType_shouldCrash() {
        mProvider.getType(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void invalid_Uri_shouldCrash() {
        final Uri invalid_Uri = new Uri.Builder()
                .scheme(ContentResolver.SCHEME_CONTENT)
                .authority(CardContentProvider.CARD_AUTHORITY)
                .path("Invalid_table")
                .build();

        mProvider.getTableFromMatch(invalid_Uri);
    }

    private ContentValues insertOneRow() {
        final ContentValues values = new ContentValues();
        values.put(CardDatabaseHelper.CardColumns.NAME, "auto_rotate");
        values.put(CardDatabaseHelper.CardColumns.TYPE, 0);
        values.put(CardDatabaseHelper.CardColumns.SCORE, 0.9);
        values.put(CardDatabaseHelper.CardColumns.SLICE_URI,
                "content://com.android.settings.slices/action/auto_rotate");
        values.put(CardDatabaseHelper.CardColumns.CATEGORY, 2);
        values.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME, "com.android.settings");
        values.put(CardDatabaseHelper.CardColumns.APP_VERSION, "1.0.0");

        return values;
    }

    private int getRowCount() {
        final ContentResolver contentResolver = mContext.getContentResolver();
        final Cursor cr = contentResolver.query(mUri, null, null, null);
        final int count = cr.getCount();
        cr.close();
        return count;
    }
}
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.android.settings.homepage;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.android.settings.testutils.SettingsRobolectricTestRunner;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;

@RunWith(SettingsRobolectricTestRunner.class)
public class CardDatabaseHelperTest {

    private Context mContext;
    private CardDatabaseHelper mCardDatabaseHelper;
    private SQLiteDatabase mDatabase;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        mCardDatabaseHelper = CardDatabaseHelper.getInstance(mContext);
        mDatabase = mCardDatabaseHelper.getWritableDatabase();
    }

    @After
    public void cleanUp() {
        CardDatabaseHelper.getInstance(mContext).close();
        CardDatabaseHelper.sCardDatabaseHelper = null;
    }

    @Test
    public void testDatabaseSchema() {
        final Cursor cursor = mDatabase.rawQuery("SELECT * FROM " + CardDatabaseHelper.CARD_TABLE,
                null);
        final String[] columnNames = cursor.getColumnNames();

        final String[] expectedNames = {
                CardDatabaseHelper.CardColumns.NAME,
                CardDatabaseHelper.CardColumns.TYPE,
                CardDatabaseHelper.CardColumns.SCORE,
                CardDatabaseHelper.CardColumns.SLICE_URI,
                CardDatabaseHelper.CardColumns.CATEGORY,
                CardDatabaseHelper.CardColumns.AVAILABILITY_URI,
                CardDatabaseHelper.CardColumns.LOCALIZED_TO_LOCALE,
                CardDatabaseHelper.CardColumns.PACKAGE_NAME,
                CardDatabaseHelper.CardColumns.APP_VERSION,
                CardDatabaseHelper.CardColumns.TITLE_RES_NAME,
                CardDatabaseHelper.CardColumns.TITLE_TEXT,
                CardDatabaseHelper.CardColumns.SUMMARY_RES_NAME,
                CardDatabaseHelper.CardColumns.SUMMARY_TEXT,
                CardDatabaseHelper.CardColumns.ICON_RES_NAME,
                CardDatabaseHelper.CardColumns.ICON_RES_ID,
                CardDatabaseHelper.CardColumns.CARD_ACTION,
                CardDatabaseHelper.CardColumns.EXPIRE_TIME_MS,
        };

        assertThat(columnNames).isEqualTo(expectedNames);
        cursor.close();
    }
}