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

Commit a0f8d231 authored by Fan Zhang's avatar Fan Zhang
Browse files

Add first impression type suggestion framework support

- category definition now supports "exclusiveExpireDays" attribute.
	- When set, the category is exclusive as long as it hasn't expired.
	  Once it expires, the category becomes a regular category.
	- If not set, the category is permanently exclusive
- For exclusive category, do not use smart suggestion dismiss rules.
  Instead, use the rule defined by suggestion itself.
- Add logic to ensure suggestions are unique, because there is nothing
  stopping suggestion to be placed in different categories.

Bug: 37947647
Test: make RunSettingsLibRoboTests -j40

Change-Id: I98126bbce6d68fcf1d739b6defc00b801075b877
parent 2a8ff264
Loading
Loading
Loading
Loading
+63 −9
Original line number Diff line number Diff line
@@ -32,20 +32,25 @@ import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.Xml;
import android.view.InflateException;

import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.TileUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class SuggestionParser {

@@ -104,8 +109,8 @@ public class SuggestionParser {
    private final String mSmartDismissControl;


    public SuggestionParser(
        Context context, SharedPreferences sharedPrefs, int orderXml, String smartDismissControl) {
    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml,
            String smartDismissControl) {
        this(
                context,
                sharedPrefs,
@@ -138,19 +143,25 @@ public class SuggestionParser {
        final int N = mSuggestionList.size();
        for (int i = 0; i < N; i++) {
            final SuggestionCategory category = mSuggestionList.get(i);
            if (category.exclusive) {
            if (category.exclusive && !isExclusiveCategoryExpired(category)) {
                // If suggestions from an exclusive category are present, parsing is stopped
                // and only suggestions from that category are displayed. Note that subsequent
                // exclusive categories are also ignored.
                List<Tile> exclusiveSuggestions = new ArrayList<>();
                readSuggestions(category, exclusiveSuggestions, isSmartSuggestionEnabled);
                final List<Tile> exclusiveSuggestions = new ArrayList<>();

                // Read suggestion and force isSmartSuggestion to be false so the rule defined
                // from each suggestion itself is used.
                readSuggestions(category, exclusiveSuggestions, false /* isSmartSuggestion */);
                if (!exclusiveSuggestions.isEmpty()) {
                    return exclusiveSuggestions;
                }
            } else {
                // Either the category is not exclusive, or the exclusiveness expired so we should
                // treat it as a normal category.
                readSuggestions(category, suggestions, isSmartSuggestionEnabled);
            }
        }
        dedupeSuggestions(suggestions);
        return suggestions;
    }

@@ -190,6 +201,22 @@ public class SuggestionParser {
        }
    }

    /**
     * Filter suggestions list so they are all unique.
     */
    private void dedupeSuggestions(List<Tile> suggestions) {
        final Set<String> intents = new ArraySet<>();
        for (int i = suggestions.size() - 1; i >= 0; i--) {
            final Tile suggestion = suggestions.get(i);
            final String intentUri = suggestion.intent.toUri(Intent.URI_INTENT_SCHEME);
            if (intents.contains(intentUri)) {
                suggestions.remove(i);
            } else {
                intents.add(intentUri);
            }
        }
    }

    @VisibleForTesting
    void readSuggestions(
            SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {
@@ -326,6 +353,25 @@ public class SuggestionParser {
        Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
    }

    /**
     * Whether or not the category's exclusiveness has expired.
     */
    private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
        final String keySetupTime = category.category + SETUP_TIME;
        final long currentTime = System.currentTimeMillis();
        if (!mSharedPrefs.contains(keySetupTime)) {
            mSharedPrefs.edit()
                    .putLong(keySetupTime, currentTime)
                    .commit();
        }
        if (category.exclusiveExpireDaysInMillis < 0) {
            // negative means never expires
            return false;
        }
        final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
        return currentTime - setupTime > category.exclusiveExpireDaysInMillis;
    }

    private boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
        String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
        if (dismissControl == null) {
@@ -384,6 +430,7 @@ public class SuggestionParser {
        public String pkg;
        public boolean multiple;
        public boolean exclusive;
        public long exclusiveExpireDaysInMillis;
    }

    private static class SuggestionOrderInflater {
@@ -394,6 +441,7 @@ public class SuggestionParser {
        private static final String ATTR_PACKAGE = "package";
        private static final String ATTR_MULTIPLE = "multiple";
        private static final String ATTR_EXCLUSIVE = "exclusive";
        private static final String ATTR_EXCLUSIVE_EXPIRE_DAYS = "exclusiveExpireDays";

        private final Context mContext;

@@ -471,6 +519,12 @@ public class SuggestionParser {
                String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
                category.exclusive =
                        !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
                String expireDaysAttr = attrs.getAttributeValue(null,
                        ATTR_EXCLUSIVE_EXPIRE_DAYS);
                long expireDays = !TextUtils.isEmpty(expireDaysAttr)
                        ? Integer.parseInt(expireDaysAttr)
                        : -1;
                category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS * expireDays;
                return category;
            } else {
                throw new IllegalArgumentException("Unknown item " + name);
+54 −9
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.settingslib;

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

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -36,19 +34,24 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.res.ResourceLoader;
import org.robolectric.res.builder.DefaultPackageManager;
import org.robolectric.res.builder.RobolectricPackageManager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

@RunWith(SettingLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SuggestionParserTest {

    private Context mContext;
    private RobolectricPackageManager mPackageManager;
    private SuggestionParser mSuggestionParser;
    private SuggestionParser.SuggestionCategory mMultipleCategory;
    private SuggestionParser.SuggestionCategory mExclusiveCategory;
    private SuggestionParser.SuggestionCategory mExpiredExclusiveCategory;
    private List<Tile> mSuggestionsBeforeDismiss;
    private List<Tile> mSuggestionsAfterDismiss;
    private SharedPreferences mPrefs;
@@ -59,6 +62,7 @@ public class SuggestionParserTest {
        RuntimeEnvironment.setRobolectricPackageManager(
                new TestPackageManager(RuntimeEnvironment.getAppResourceLoader()));
        mContext = RuntimeEnvironment.application;
        mPackageManager = RuntimeEnvironment.getRobolectricPackageManager();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        mSuggestion = new Tile();
        mSuggestion.intent = new Intent("action");
@@ -70,21 +74,36 @@ public class SuggestionParserTest {
        mExclusiveCategory = new SuggestionParser.SuggestionCategory();
        mExclusiveCategory.category = "category2";
        mExclusiveCategory.exclusive = true;
        mSuggestionParser = new SuggestionParser(
                mContext, mPrefs, Arrays.asList(mMultipleCategory, mExclusiveCategory), "0,0");
        mExpiredExclusiveCategory = new SuggestionParser.SuggestionCategory();
        mExpiredExclusiveCategory.category = "category3";
        mExpiredExclusiveCategory.exclusive = true;
        mExpiredExclusiveCategory.exclusiveExpireDaysInMillis = 0;

        mSuggestionParser = new SuggestionParser(mContext, mPrefs,
                Arrays.asList(mMultipleCategory, mExclusiveCategory, mExpiredExclusiveCategory),
                "0,0");

        ResolveInfo info1 = TileUtilsTest.newInfo(true, "category1");
        info1.activityInfo.packageName = "pkg";
        ResolveInfo infoDupe1 = TileUtilsTest.newInfo(true, "category1");
        infoDupe1.activityInfo.packageName = "pkg";

        ResolveInfo info2 = TileUtilsTest.newInfo(true, "category1");
        info2.activityInfo.packageName = "pkg2";
        ResolveInfo info3 = TileUtilsTest.newInfo(true, "category2");
        info3.activityInfo.packageName = "pkg3";
        ResolveInfo info4 = TileUtilsTest.newInfo(true, "category3");
        info4.activityInfo.packageName = "pkg4";

        Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
        Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info1);
        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info2);
        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent2, info3);
        Intent intent3 = new Intent(Intent.ACTION_MAIN).addCategory("category3");

        mPackageManager.addResolveInfoForIntent(intent1, info1);
        mPackageManager.addResolveInfoForIntent(intent1, info2);
        mPackageManager.addResolveInfoForIntent(intent1, infoDupe1);
        mPackageManager.addResolveInfoForIntent(intent2, info3);
        mPackageManager.addResolveInfoForIntent(intent3, info4);
    }

    @Test
@@ -115,16 +134,42 @@ public class SuggestionParserTest {
    }

    @Test
    public void testGetSuggestion_exclusiveNotAvailable() {
        RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
    public void testGetSuggestion_exclusiveNotAvailable_onlyRegularCategoryAndNoDupe() {
        mPackageManager.removeResolveInfosForIntent(
                new Intent(Intent.ACTION_MAIN).addCategory("category2"),
                "pkg3");
        mPackageManager.removeResolveInfosForIntent(
                new Intent(Intent.ACTION_MAIN).addCategory("category3"),
                "pkg4");

        // If exclusive item is not available, the other categories should be shown
        final List<Tile> suggestions = mSuggestionParser.getSuggestions();

        assertThat(suggestions).hasSize(2);
        assertThat(suggestions.get(0).category).isEqualTo("category1");
        assertThat(suggestions.get(0).intent.getComponent().getPackageName()).isEqualTo("pkg");
        assertThat(suggestions.get(1).category).isEqualTo("category1");
        assertThat(suggestions.get(1).intent.getComponent().getPackageName()).isEqualTo("pkg2");
    }

    @Test
    public void testGetSuggestion_exclusiveExpiredAvailable_shouldLoadWithRegularCategory() {
        // First remove permanent exclusive
        mPackageManager.removeResolveInfosForIntent(
                new Intent(Intent.ACTION_MAIN).addCategory("category2"),
                "pkg3");
        // Set the other exclusive to be expired.
        mPrefs.edit()
                .putLong(mExpiredExclusiveCategory.category + "_setup_time",
                        System.currentTimeMillis() - 1000)
                .commit();

        // If exclusive is expired, they should be shown together with the other categories
        final List<Tile> suggestions = mSuggestionParser.getSuggestions();
        assertThat(suggestions).hasSize(3);
        assertThat(suggestions.get(0).category).isEqualTo("category1");
        assertThat(suggestions.get(1).category).isEqualTo("category1");
        assertThat(suggestions.get(2).category).isEqualTo("category3");
    }

    @Test