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 Original line Diff line number Diff line
@@ -15,21 +15,28 @@
 */
 */
package com.android.settingslib;
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.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.SharedPreferences;
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.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.util.Pair;
import android.util.Xml;
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 android.view.InflateException;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.TileUtils;
import com.android.settingslib.drawer.TileUtils;
@@ -53,6 +60,20 @@ public class SuggestionParser {
    // If defined and not true, do not should optional step.
    // If defined and not true, do not should optional step.
    private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
    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.
     * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
     * For instance:
     * For instance:
@@ -75,7 +96,7 @@ public class SuggestionParser {


    private final Context mContext;
    private final Context mContext;
    private final List<SuggestionCategory> mSuggestionList;
    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;
    private final SharedPreferences mSharedPrefs;


    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
@@ -85,6 +106,14 @@ public class SuggestionParser {
        mSharedPrefs = sharedPrefs;
        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() {
    public List<Tile> getSuggestions() {
        List<Tile> suggestions = new ArrayList<>();
        List<Tile> suggestions = new ArrayList<>();
        final int N = mSuggestionList.size();
        final int N = mSuggestionList.size();
@@ -111,23 +140,30 @@ public class SuggestionParser {
        return false;
        return false;
    }
    }


    private void readSuggestions(SuggestionCategory category, List<Tile> suggestions) {
    @VisibleForTesting
        int countBefore = suggestions.size();
    public void filterSuggestions(List<Tile> suggestions, int countBefore) {
        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);
        for (int i = countBefore; i < suggestions.size(); i++) {
        for (int i = countBefore; i < suggestions.size(); i++) {
            if (!isAvailable(suggestions.get(i)) ||
            if (!isAvailable(suggestions.get(i)) ||
                    !isSupported(suggestions.get(i)) ||
                    !isSupported(suggestions.get(i)) ||
                    !satisifesRequiredUserType(suggestions.get(i)) ||
                    !satisfiesRequiredAccount(suggestions.get(i)) ||
                    !satisfiesRequiredAccount(suggestions.get(i)) ||
                    !satisfiesConnectivity(suggestions.get(i)) ||
                    isDismissed(suggestions.get(i))) {
                    isDismissed(suggestions.get(i))) {
                suggestions.remove(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 (!category.multiple && suggestions.size() > (countBefore + 1)) {
            // If there are too many, remove them all and only re-add the one with the highest
            // If there are too many, remove them all and only re-add the one with the highest
            // priority.
            // priority.
@@ -146,32 +182,77 @@ public class SuggestionParser {
    }
    }


    private boolean isAvailable(Tile suggestion) {
    private boolean isAvailable(Tile suggestion) {
        String featureRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
        final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
        if (featureRequired != null) {
        if (featuresRequired != null) {
            return mContext.getPackageManager().hasSystemFeature(featureRequired);
            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;
        return true;
    }
    }


    public boolean satisfiesRequiredAccount(Tile suggestion) {
    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) {
        if (requiredAccountType == null) {
            return true;
            return true;
        }
        }
        AccountManager accountManager = AccountManager.get(mContext);
        AccountManager accountManager = AccountManager.get(mContext);
        Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
        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) {
    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 {
        try {
            if (suggestion.intent == null) {
            if (suggestion.intent == null) {
                return false;
                return false;
            }
            }
            final Resources res = mContext.getPackageManager().getResourcesForActivity(
            final Resources res = mContext.getPackageManager().getResourcesForActivity(
                    suggestion.intent.getComponent());
                    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) {
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
            return false;
            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) {
    public boolean isCategoryDone(String category) {
        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
+40 −0
Original line number Original line Diff line number Diff line
@@ -34,9 +34,11 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.provider.Settings.Global;
import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Pair;


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


@@ -155,6 +157,32 @@ public class TileUtilsTest {
        assertThat(outTiles.isEmpty()).isTrue();
        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
    @Test
    public void getCategories_shouldHandleExtraIntentAction() {
    public void getCategories_shouldHandleExtraIntentAction() {
        final String testCategory = "category1";
        final String testCategory = "category1";
@@ -309,4 +337,16 @@ public class TileUtilsTest {
        }
        }
        return info;
        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);
        }
    }
}
}