1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.widget;
17 
18 import static android.content.res.Resources.ID_NULL;
19 
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.content.res.XmlResourceParser;
24 import android.util.ArrayMap;
25 import android.util.AttributeSet;
26 import android.util.SparseArray;
27 import android.util.Xml;
28 
29 import androidx.annotation.DrawableRes;
30 import androidx.annotation.StringRes;
31 
32 import com.android.launcher3.R;
33 import com.android.launcher3.util.IntSet;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.IOException;
39 import java.util.Map;
40 
41 /** A helper class to parse widget sections (categories) resource overlay. */
42 public final class WidgetSections {
43     /** The package is not categorized in the widget tray. */
44     public static final int NO_CATEGORY = -1;
45 
46     private static final String TAG_SECTION_NAME = "section";
47     private static final String TAG_WIDGET_NAME = "widget";
48 
49     private static SparseArray<WidgetSection> sWidgetSections;
50     private static Map<ComponentName, IntSet> sWidgetsToCategories;
51 
52     /** Returns a list of widget sections that are shown in the widget picker. */
getWidgetSections(Context context)53     public static synchronized SparseArray<WidgetSection> getWidgetSections(Context context) {
54         if (sWidgetSections != null) {
55             return sWidgetSections;
56         }
57         parseWidgetSectionsXml(context);
58         return sWidgetSections;
59     }
60 
61     /** Returns a map which maps app widget providers to app widget categories. */
getWidgetsToCategory( Context context)62     public static synchronized Map<ComponentName, IntSet> getWidgetsToCategory(
63             Context context) {
64         if (sWidgetsToCategories != null) {
65             return sWidgetsToCategories;
66         }
67         parseWidgetSectionsXml(context);
68         return sWidgetsToCategories;
69     }
70 
parseWidgetSectionsXml(Context context)71     private static synchronized void parseWidgetSectionsXml(Context context) {
72         SparseArray<WidgetSection> widgetSections = new SparseArray();
73         Map<ComponentName, IntSet> widgetsToCategories = new ArrayMap<>();
74         try (XmlResourceParser parser = context.getResources().getXml(R.xml.widget_sections)) {
75             final int depth = parser.getDepth();
76             int type;
77             while (((type = parser.next()) != XmlPullParser.END_TAG
78                     || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
79                 if ((type == XmlPullParser.START_TAG)
80                         && TAG_SECTION_NAME.equals(parser.getName())) {
81                     AttributeSet sectionAttributes = Xml.asAttributeSet(parser);
82                     WidgetSection section = new WidgetSection(context, sectionAttributes);
83                     final int sectionDepth = parser.getDepth();
84                     while (((type = parser.next()) != XmlPullParser.END_TAG
85                                     || parser.getDepth() > sectionDepth)
86                             && type != XmlPullParser.END_DOCUMENT) {
87                         if ((type == XmlPullParser.START_TAG)
88                                 && TAG_WIDGET_NAME.equals(parser.getName())) {
89                             TypedArray a = context.obtainStyledAttributes(
90                                     Xml.asAttributeSet(parser), R.styleable.WidgetSections);
91                             ComponentName provider = ComponentName.unflattenFromString(
92                                     a.getString(R.styleable.WidgetSections_provider));
93                             boolean alsoKeepInApp = a.getBoolean(
94                                     R.styleable.WidgetSections_alsoKeepInApp,
95                                     /* defValue= */ false);
96                             final IntSet categories;
97                             if (widgetsToCategories.containsKey(provider)) {
98                                 categories = widgetsToCategories.get(provider);
99                             } else {
100                                 categories = new IntSet();
101                                 widgetsToCategories.put(provider, categories);
102                             }
103                             if (alsoKeepInApp) {
104                                 categories.add(NO_CATEGORY);
105                             }
106                             categories.add(section.mCategory);
107                         }
108                     }
109                     widgetSections.put(section.mCategory, section);
110                 }
111             }
112             sWidgetSections = widgetSections;
113             sWidgetsToCategories = widgetsToCategories;
114         } catch (IOException | XmlPullParserException e) {
115             throw new RuntimeException(e);
116         }
117     }
118 
119     /** A data class which contains a widget section's information. */
120     public static final class WidgetSection {
121         public final int mCategory;
122         @StringRes
123         public final int mSectionTitle;
124         @DrawableRes
125         public final int mSectionDrawable;
126 
WidgetSection(Context context, AttributeSet attrs)127         public WidgetSection(Context context, AttributeSet attrs) {
128             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WidgetSections);
129             mCategory = a.getInt(R.styleable.WidgetSections_category, NO_CATEGORY);
130             mSectionTitle = a.getResourceId(R.styleable.WidgetSections_sectionTitle, ID_NULL);
131             mSectionDrawable = a.getResourceId(R.styleable.WidgetSections_sectionDrawable, ID_NULL);
132         }
133     }
134 }
135