CarNavigationBarController.java revision 1c6d0589f1c6429ca84402227cd5954479cf66ed
1/*
2 * Copyright (C) 2015 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 */
16package com.android.systemui.statusbar.car;
17
18import android.content.Context;
19import android.content.Intent;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.drawable.Drawable;
25import android.support.v4.util.SimpleArrayMap;
26import android.view.View;
27import android.widget.LinearLayout;
28import com.android.systemui.R;
29import com.android.systemui.statusbar.phone.ActivityStarter;
30
31import java.net.URISyntaxException;
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * A controller to populate data for CarNavigationBarView and handle user interactions.
37 * <p/>
38 * Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can customize
39 * the navigation buttons by updating arrays_car.xml appropriately in an overlay.
40 */
41class CarNavigationBarController {
42
43    // Each facet of the navigation bar maps to a set of package names or categories defined in
44    // arrays_car.xml. Package names for a given facet are delimited by ";"
45    private static final String FACET_FILTER_DEMILITER = ";";
46
47    private Context mContext;
48    private CarNavigationBarView mNavBar;
49    private ActivityStarter mActivityStarter;
50
51    // Set of categories each facet will filter on.
52    private List<String[]> mFacetCategories = new ArrayList<String[]>();
53    // Set of package names each facet will filter on.
54    private List<String[]> mFacetPackages = new ArrayList<String[]>();
55
56    private SimpleArrayMap<String, Integer> mFacetCategoryMap
57            = new SimpleArrayMap<String, Integer>();
58    private SimpleArrayMap<String, Integer> mFacetPackageMap
59            = new SimpleArrayMap<String, Integer>();
60
61    private List<Intent> mIntents = new ArrayList<Intent>();
62    private List<Intent> mLongPressIntents = new ArrayList<Intent>();
63
64    private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>();
65
66    private int mCurrentFacetIndex;
67
68    public CarNavigationBarController(Context context,
69                                      CarNavigationBarView navBar,
70                                      ActivityStarter activityStarter) {
71        mContext = context;
72        mNavBar = navBar;
73        mActivityStarter = activityStarter;
74        bind();
75    }
76
77    public void taskChanged(String packageName) {
78        // If the package name belongs to a filter, then highlight appropriate button in
79        // the navigation bar.
80        if (mFacetPackageMap.containsKey(packageName)) {
81            setCurrentFacet(mFacetPackageMap.get(packageName));
82        }
83
84        // Check if the package matches any of the categories for the facets
85        String category = getPackageCategory(packageName);
86        if (category != null) {
87            setCurrentFacet(mFacetCategoryMap.get(category));
88        }
89    }
90
91    private void bind() {
92        // Read up arrays_car.xml and populate the navigation bar here.
93        Resources r = mContext.getResources();
94        TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons);
95        TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris);
96        TypedArray longpressIntents =
97                r.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
98        TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters);
99
100        TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters);
101
102        if (icons.length() != intents.length()
103                || icons.length() != longpressIntents.length()
104                || icons.length() != facetPackageNames.length()
105                || icons.length() != facetCategories.length()) {
106            throw new RuntimeException("car_facet array lengths do not match");
107        }
108
109        for (int i = 0; i < icons.length(); i++) {
110            Drawable icon = icons.getDrawable(i);
111            try {
112                mIntents.add(i,
113                        Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME));
114
115                String longpressUri = longpressIntents.getString(i);
116                boolean hasLongpress = !longpressUri.isEmpty();
117                if (hasLongpress) {
118                    mLongPressIntents.add(i,
119                            Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME));
120                }
121
122                CarNavigationButton button = createNavButton(icon, i, hasLongpress);
123                mNavButtons.add(button);
124                mNavBar.addButton(button, createNavButton(icon, i, hasLongpress));
125
126                initFacetFilterMaps(i,
127                        facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
128                        facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
129            } catch (URISyntaxException e) {
130                throw new RuntimeException("Malformed intent uri", e);
131            }
132        }
133    }
134
135    private void initFacetFilterMaps(int id, String[] packageNames, String[] categories){
136        mFacetCategories.add(categories);
137        for (int i = 0; i < categories.length; i++) {
138            mFacetCategoryMap.put(categories[i], id);
139        }
140
141        mFacetPackages.add(packageNames);
142        for (int i = 0; i < packageNames.length; i++) {
143            mFacetPackageMap.put(packageNames[i], id);
144        }
145    }
146
147    private String getPackageCategory(String packageName) {
148        PackageManager pm = mContext.getPackageManager();
149        int size = mFacetCategories.size();
150        // For each facet, check if the given package name matches one of its categories
151        for (int i = 0; i < size; i++) {
152            String[] categories = mFacetCategories.get(i);
153            for (int j = 0; j < categories.length; j++) {
154                String category = categories[j];
155                Intent intent = new Intent();
156                intent.setPackage(packageName);
157                intent.setAction(Intent.ACTION_MAIN);
158                intent.addCategory(category);
159                List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
160                if (list.size() > 0) {
161                    // Cache this package name into facetPackageMap, so we won't have to query
162                    // all categories next time this package name shows up.
163                    mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
164                    return category;
165                }
166            }
167        }
168        return null;
169    }
170
171    /**
172     * Helper method to check if a given facet has multiple packages associated with it.
173     * This can be resource defined package names or package names filtered by facet category.
174     */
175    private boolean facetHasMultiplePackages(int index) {
176        PackageManager pm = mContext.getPackageManager();
177
178        // Check if the packages defined for the filter actually exists on the device
179        String[] packages = mFacetPackages.get(index);
180        if (packages.length > 1) {
181            int count = 0;
182            for (int i = 0; i < packages.length; i++) {
183                count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
184                if (count > 1) {
185                    return true;
186                }
187            }
188        }
189
190        // If there weren't multiple packages defined for the facet, check the categories
191        // and see if they resolve to multiple package names
192        String categories[] = mFacetCategories.get(index);
193
194        int count = 0;
195        for (int i = 0; i < categories.length; i++) {
196            String category = categories[i];
197            Intent intent = new Intent();
198            intent.setAction(Intent.ACTION_MAIN);
199            intent.addCategory(category);
200            count += pm.queryIntentActivities(intent, 0).size();
201            if (count > 1) {
202                return true;
203            }
204        }
205        return false;
206    }
207
208    private void setCurrentFacet(int index) {
209        if (index == mCurrentFacetIndex) {
210            return;
211        }
212
213        if (mNavButtons.get(mCurrentFacetIndex) != null) {
214            mNavButtons.get(mCurrentFacetIndex)
215                    .setSelected(false /* selected */, false /* showMoreIcon */);
216        }
217
218        if (mNavButtons.get(index) != null) {
219            mNavButtons.get(index).setSelected(true /* selected */,
220                    facetHasMultiplePackages(index)  /* showMoreIcon */);
221        }
222        mCurrentFacetIndex = index;
223    }
224
225    private CarNavigationButton createNavButton(Drawable icon, final int id,
226                                                boolean longClickEnabled) {
227        CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
228                R.layout.car_navigation_button, null);
229        button.setResources(icon);
230        LinearLayout.LayoutParams lp =
231                new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
232        button.setLayoutParams(lp);
233
234        button.setOnClickListener(new View.OnClickListener() {
235            @Override
236            public void onClick(View v) {
237                setCurrentFacet(id);
238                onFacetClicked(id);
239            }
240        });
241
242        if (longClickEnabled) {
243            button.setLongClickable(true);
244            button.setOnLongClickListener(new View.OnLongClickListener() {
245                @Override
246                public boolean onLongClick(View v) {
247                    onFacetLongClicked(id);
248                    setCurrentFacet(id);
249                    return true;
250                }
251            });
252        } else {
253            button.setLongClickable(false);
254        }
255        return button;
256    }
257
258    private void startActivity(Intent intent) {
259        if (mActivityStarter != null && intent != null) {
260            mActivityStarter.startActivity(intent, true);
261        }
262    }
263
264    private void onFacetClicked(int index) {
265        // TODO: determine what data to pass to the trampoline, so it can start
266        // the default app or the lens picker.
267        startActivity(mIntents.get(index));
268    }
269
270    private void onFacetLongClicked(int index) {
271        // TODO: determine what data to pass to the trampoline, so it can start
272        // the default app or the lens picker.
273        startActivity(mLongPressIntents.get(index));
274    }
275}
276