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

Commit d3f5010a authored by Shamali Patwa's avatar Shamali Patwa Committed by Android (Google) Code Review
Browse files

Merge changes from topic "categories" into main

* changes:
  Add a default widget category provider that uses application category.
  Add a feature flag for displaying categorized widget recommendations.
parents 8ee9076f c5fbb42d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -140,3 +140,10 @@ flag {
    description: "Tie unfold animation with state animation"
    bug: "297057373"
}

flag {
  name: "enable_categorized_widget_suggestions"
  namespace: "launcher"
  description: "Enables widget suggestions in widget picker to be displayed in categories"
  bug: "318410881"
}
+8 −0
Original line number Diff line number Diff line
@@ -194,6 +194,11 @@

    <string-array name="filtered_components" ></string-array>

    <!-- Widget component names to be included in weather category of widget suggestions. -->
    <string-array name="weather_recommendations"></string-array>
    <!-- Widget component names to be included in fitness category of widget suggestions. -->
    <string-array name="fitness_recommendations"></string-array>

    <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
    <string name="color_generator_class" translatable="false"/>

@@ -252,6 +257,9 @@
    <!--  Used for custom widgets  -->
    <array name="custom_widget_providers"/>

    <!--  Used for determining category of a widget presented in widget recommendations. -->
    <string name="widget_recommendation_category_provider_class" translatable="false"></string>

    <!-- Embed parameters -->
    <dimen name="activity_split_ratio"  format="float">0.5</dimen>
    <integer name="min_width_split">720</integer>
+6 −0
Original line number Diff line number Diff line
@@ -71,6 +71,12 @@
    <!-- Widget suggestions header title in the full widgets picker for large screen devices
    in landscape mode. [CHAR_LIMIT=50] -->
    <string name="suggested_widgets_header_title">Suggestions</string>
    <string name="productivity_widget_recommendation_category_label">Boost your day</string>
    <string name="news_widget_recommendation_category_label">News For You</string>
    <string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
    <string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
    <string name="weather_widget_recommendation_category_label">Stay Ahead of the Weather</string>
    <string name="others_widget_recommendation_category_label">You Might Also Like</string>
    <!-- Label for showing the number of widgets an app has in the full widgets picker.
         [CHAR_LIMIT=25][ICU SYNTAX] -->
    <string name="widgets_count">
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.widget.picker;

import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import java.util.Objects;

/**
 * A category of widget recommendations displayed in the widget picker (launched from "Widgets"
 * option in the pop-up opened on long press of launcher workspace).
 */
public class WidgetRecommendationCategory implements Comparable<WidgetRecommendationCategory> {
    /** Resource id that holds the user friendly label for the category. */
    @StringRes
    public final int categoryTitleRes;
    /**
     * Relative order of this category with respect to other categories.
     *
     * <p>Category with lowest order is displayed first in the recommendations section.</p>
     */
    public final int order;

    public WidgetRecommendationCategory(@StringRes int categoryTitleRes, int order) {
        this.categoryTitleRes = categoryTitleRes;
        this.order = order;
    }

    @Override
    public int hashCode() {
        return Objects.hash(categoryTitleRes, order);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (!(obj instanceof WidgetRecommendationCategory category)) {
            return false;
        }
        return categoryTitleRes == category.categoryTitleRes
                && order == category.order;
    }

    @Override
    public int compareTo(WidgetRecommendationCategory widgetRecommendationCategory) {
        return order - widgetRecommendationCategory.order;
    }
}
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.widget.picker;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;

import androidx.annotation.WorkerThread;

import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;

/**
 * A {@link ResourceBasedOverride} that categorizes widget recommendations.
 *
 * <p>Override the {@code widget_recommendation_category_provider_class} resource to provide your
 * own implementation. Method {@code getWidgetRecommendationCategory} is called per widget to get
 * the category.</p>
 */
public class WidgetRecommendationCategoryProvider implements ResourceBasedOverride {
    private static final String TAG = "WidgetRecommendationCategoryProvider";

    /**
     * Retrieve instance of this object that can be overridden in runtime based on the build
     * variant of the application.
     */
    public static WidgetRecommendationCategoryProvider newInstance(Context context) {
        Preconditions.assertWorkerThread();
        return Overrides.getObject(
                WidgetRecommendationCategoryProvider.class, context.getApplicationContext(),
                R.string.widget_recommendation_category_provider_class);
    }

    /**
     * Returns a {@link WidgetRecommendationCategory} for the provided widget item that can be used
     * to display the recommendation grouped by categories.
     */
    @WorkerThread
    public WidgetRecommendationCategory getWidgetRecommendationCategory(Context context,
            WidgetItem item) {
        // This is a default implementation that uses application category to derive the category to
        // be displayed. The implementation can be overridden in individual launcher customization
        // via the overridden WidgetRecommendationCategoryProvider resource.

        Preconditions.assertWorkerThread();
        PackageManager pm = context.getPackageManager();
        if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
            String widgetComponentName = item.widgetInfo.getComponent().getClassName();
            try {
                int predictionCategory = pm.getApplicationInfo(
                        item.widgetInfo.getComponent().getPackageName(), 0 /* flags */).category;
                return getCategoryFromApplicationCategory(context, predictionCategory,
                        widgetComponentName);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "Failed to retrieve application category when determining the "
                        + "widget category for " + widgetComponentName, e);
            }
        }
        return null;
    }

    /** Maps application category to an appropriate displayable category. */
    private static WidgetRecommendationCategory getCategoryFromApplicationCategory(
            Context context, int applicationCategory, String componentName) {
        if (applicationCategory == ApplicationInfo.CATEGORY_PRODUCTIVITY) {
            return new WidgetRecommendationCategory(
                    R.string.productivity_widget_recommendation_category_label, /*order=*/0);
        }

        if (applicationCategory == ApplicationInfo.CATEGORY_NEWS) {
            return new WidgetRecommendationCategory(
                    R.string.news_widget_recommendation_category_label, /*order=*/1);
        }

        if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL
                || applicationCategory == ApplicationInfo.CATEGORY_AUDIO
                || applicationCategory == ApplicationInfo.CATEGORY_VIDEO
                || applicationCategory == ApplicationInfo.CATEGORY_IMAGE) {
            return new WidgetRecommendationCategory(
                    R.string.social_and_entertainment_widget_recommendation_category_label,
                    /*order=*/4);
        }

        // Fitness & weather categories don't map to a specific application category, so, we
        // maintain an allowlist.
        String[] weatherRecommendationAllowlist =
                context.getResources().getStringArray(R.array.weather_recommendations);
        for (String allowedWeatherComponentName : weatherRecommendationAllowlist) {
            if (componentName.equalsIgnoreCase(allowedWeatherComponentName)) {
                return new WidgetRecommendationCategory(
                        R.string.weather_widget_recommendation_category_label, /*order=*/2);
            }
        }

        String[] fitnessRecommendationAllowlist =
                context.getResources().getStringArray(R.array.fitness_recommendations);
        for (String allowedFitnessComponentName : fitnessRecommendationAllowlist) {
            if (componentName.equalsIgnoreCase(allowedFitnessComponentName)) {
                return new WidgetRecommendationCategory(
                        R.string.fitness_widget_recommendation_category_label, /*order=*/3);
            }
        }

        return new WidgetRecommendationCategory(
                R.string.others_widget_recommendation_category_label, /*order=*/5);
    }

}
Loading