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