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

Commit 081d576c authored by Ido Ofir's avatar Ido Ofir Committed by Android (Google) Code Review
Browse files

Merge "Adding missing filters to suggested actions in Settings that that they...

Merge "Adding missing filters to suggested actions in Settings that that they match the Optional Steps fliters in setup wizard. Also added some more logging."
parents 2591ce4d a560cd94
Loading
Loading
Loading
Loading
+119 −22
Original line number Diff line number Diff line
@@ -15,21 +15,28 @@
 */
package com.android.settingslib;

import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.Xml;
import android.provider.Settings;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.view.InflateException;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.TileUtils;
@@ -53,6 +60,20 @@ public class SuggestionParser {
    // If defined and not true, do not should optional step.
    private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";

    // If defined, only display this optional step if the current user is of that type.
    private static final String META_DATA_REQUIRE_USER_TYPE =
            "com.android.settings.require_user_type";

    // If defined, only display this optional step if a connection is available.
    private static final String META_DATA_IS_CONNECTION_REQUIRED =
            "com.android.settings.require_connection";

    // The valid values that setup wizard recognizes for differentiating user types.
    private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
    private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
    private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
    private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";

    /**
     * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
     * For instance:
@@ -75,7 +96,7 @@ public class SuggestionParser {

    private final Context mContext;
    private final List<SuggestionCategory> mSuggestionList;
    private final ArrayMap<Pair<String, String>, Tile> addCache = new ArrayMap<>();
    private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
    private final SharedPreferences mSharedPrefs;

    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
@@ -85,6 +106,14 @@ public class SuggestionParser {
        mSharedPrefs = sharedPrefs;
    }

    @VisibleForTesting
    public SuggestionParser(Context context, SharedPreferences sharedPrefs) {
        mContext = context;
        mSuggestionList = new ArrayList<SuggestionCategory>();
        mSharedPrefs = sharedPrefs;
        Log.wtf(TAG, "Only use this constructor for testing");
    }

    public List<Tile> getSuggestions() {
        List<Tile> suggestions = new ArrayList<>();
        final int N = mSuggestionList.size();
@@ -111,23 +140,30 @@ public class SuggestionParser {
        return false;
    }

    private void readSuggestions(SuggestionCategory category, List<Tile> suggestions) {
        int countBefore = suggestions.size();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(category.category);
        if (category.pkg != null) {
            intent.setPackage(category.pkg);
        }
        TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
                addCache, null, suggestions, true, false);
    @VisibleForTesting
    public void filterSuggestions(List<Tile> suggestions, int countBefore) {
        for (int i = countBefore; i < suggestions.size(); i++) {
            if (!isAvailable(suggestions.get(i)) ||
                    !isSupported(suggestions.get(i)) ||
                    !satisifesRequiredUserType(suggestions.get(i)) ||
                    !satisfiesRequiredAccount(suggestions.get(i)) ||
                    !satisfiesConnectivity(suggestions.get(i)) ||
                    isDismissed(suggestions.get(i))) {
                suggestions.remove(i--);
            }
        }
    }

    private void readSuggestions(SuggestionCategory category, List<Tile> suggestions) {
        int countBefore = suggestions.size();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(category.category);
        if (category.pkg != null) {
            intent.setPackage(category.pkg);
        }
        TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
                mAddCache, null, suggestions, true, false);
        filterSuggestions(suggestions, countBefore);
        if (!category.multiple && suggestions.size() > (countBefore + 1)) {
            // If there are too many, remove them all and only re-add the one with the highest
            // priority.
@@ -146,32 +182,77 @@ public class SuggestionParser {
    }

    private boolean isAvailable(Tile suggestion) {
        String featureRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
        if (featureRequired != null) {
            return mContext.getPackageManager().hasSystemFeature(featureRequired);
        final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
        if (featuresRequired != null) {
            for (String feature : featuresRequired.split(",")) {
                if (TextUtils.isEmpty(feature)) {
                    Log.w(TAG, "Found empty substring when parsing required features: "
                            + featuresRequired);
                } else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
                    Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
                    return false;
                }
            }
        }
        return true;
    }

    @RequiresPermission(Manifest.permission.MANAGE_USERS)
    private boolean satisifesRequiredUserType(Tile suggestion) {
        final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
        if (requiredUser != null) {
            final UserManager userManager = mContext.getSystemService(UserManager.class);
            UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
            for (String userType : requiredUser.split("\\|")) {
                final boolean primaryUserCondtionMet = userInfo.isPrimary()
                        && META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
                final boolean adminUserConditionMet = userInfo.isAdmin()
                        && META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
                final boolean guestUserCondtionMet = userInfo.isGuest()
                        && META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
                final boolean restrictedUserCondtionMet = userInfo.isRestricted()
                        && META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
                if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
                        || restrictedUserCondtionMet) {
                    return true;
                }
            }
            Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
            return false;
        }
        return true;
    }

    public boolean satisfiesRequiredAccount(Tile suggestion) {
        String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
        final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
        if (requiredAccountType == null) {
            return true;
        }
        AccountManager accountManager = AccountManager.get(mContext);
        Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
        return accounts.length > 0;
        boolean satisfiesRequiredAccount = accounts.length > 0;
        if (!satisfiesRequiredAccount) {
            Log.i(TAG, suggestion.title + " requires unavailable account type "
                    + requiredAccountType);
        }
        return satisfiesRequiredAccount;
    }

    public boolean isSupported(Tile suggestion) {
        int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
        final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
        try {
            if (suggestion.intent == null) {
                return false;
            }
            final Resources res = mContext.getPackageManager().getResourcesForActivity(
                    suggestion.intent.getComponent());
            return isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
            boolean isSupported =
                    isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
            if (!isSupported) {
                Log.i(TAG, suggestion.title + " requires unsupported resource "
                        + isSupportedResource);
            }
            return isSupported;
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
            return false;
@@ -181,6 +262,22 @@ public class SuggestionParser {
        }
    }

    private boolean satisfiesConnectivity(Tile suggestion) {
        final boolean isConnectionRequired =
                suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
        if (!isConnectionRequired) {
          return true;
        }
        ConnectivityManager cm =
                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
        if (!satisfiesConnectivity) {
            Log.i(TAG, suggestion.title + " is missing required connection.");
        }
        return satisfiesConnectivity;
    }

    public boolean isCategoryDone(String category) {
        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
+40 −0
Original line number Diff line number Diff line
@@ -34,9 +34,11 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;

import com.android.settingslib.SuggestionParser;
import com.android.settingslib.TestConfig;
import static org.mockito.Mockito.atLeastOnce;

@@ -155,6 +157,32 @@ public class TileUtilsTest {
        assertThat(outTiles.isEmpty()).isTrue();
    }

    @Test
    public void getTilesForIntent_shouldSkipFilteredApps() {
        final String testCategory = "category1";
        Intent intent = new Intent();
        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
        List<Tile> outTiles = new ArrayList<>();
        List<ResolveInfo> info = new ArrayList<>();
        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
                URI_GET_SUMMARY);
        addMetadataToInfo(resolveInfo, "com.android.settings.require_account", "com.google");
        addMetadataToInfo(resolveInfo, "com.android.settings.require_connection", "true");
        info.add(resolveInfo);

        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
                .thenReturn(info);

        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
                null /* defaultCategory */, outTiles, false /* usePriority */,
                false /* checkCategory */);

        assertThat(outTiles.size()).isEqualTo(1);
        SuggestionParser parser = new SuggestionParser(mContext, null);
        parser.filterSuggestions(outTiles, 0);
        assertThat(outTiles.size()).isEqualTo(0);
    }

    @Test
    public void getCategories_shouldHandleExtraIntentAction() {
        final String testCategory = "category1";
@@ -309,4 +337,16 @@ public class TileUtilsTest {
        }
        return info;
    }

    private void addMetadataToInfo(ResolveInfo info, String key, String value) {
        if (!TextUtils.isEmpty(key)) {
            if (info.activityInfo == null) {
                info.activityInfo = new ActivityInfo();
            }
            if (info.activityInfo.metaData == null) {
                info.activityInfo.metaData = new Bundle();
            }
            info.activityInfo.metaData.putString(key, value);
        }
    }
}