| /* |
| * Copyright (C) 2015 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.systemui.statusbar.car; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Drawable; |
| import android.support.v4.util.SimpleArrayMap; |
| import android.view.View; |
| import android.widget.LinearLayout; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.statusbar.phone.ActivityStarter; |
| |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * A controller to populate data for CarNavigationBarView and handle user interactions. |
| * <p/> |
| * Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can customize |
| * the navigation buttons by updating arrays_car.xml appropriately in an overlay. |
| */ |
| class CarNavigationBarController { |
| private static final String EXTRA_FACET_CATEGORIES = "categories"; |
| private static final String EXTRA_FACET_PACKAGES = "packages"; |
| private static final String EXTRA_FACET_ID = "filter_id"; |
| private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker"; |
| |
| // Each facet of the navigation bar maps to a set of package names or categories defined in |
| // arrays_car.xml. Package names for a given facet are delimited by ";" |
| private static final String FACET_FILTER_DEMILITER = ";"; |
| |
| private Context mContext; |
| private CarNavigationBarView mNavBar; |
| private ActivityStarter mActivityStarter; |
| |
| // Set of categories each facet will filter on. |
| private List<String[]> mFacetCategories = new ArrayList<String[]>(); |
| // Set of package names each facet will filter on. |
| private List<String[]> mFacetPackages = new ArrayList<String[]>(); |
| |
| private SimpleArrayMap<String, Integer> mFacetCategoryMap |
| = new SimpleArrayMap<String, Integer>(); |
| private SimpleArrayMap<String, Integer> mFacetPackageMap |
| = new SimpleArrayMap<String, Integer>(); |
| |
| private List<Intent> mIntents; |
| private List<Intent> mLongPressIntents; |
| |
| private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>(); |
| |
| private int mCurrentFacetIndex; |
| private String mCurrentPackageName; |
| |
| public CarNavigationBarController(Context context, |
| CarNavigationBarView navBar, |
| ActivityStarter activityStarter) { |
| mContext = context; |
| mNavBar = navBar; |
| mActivityStarter = activityStarter; |
| bind(); |
| } |
| |
| public void taskChanged(String packageName) { |
| mCurrentPackageName = packageName; |
| // If the package name belongs to a filter, then highlight appropriate button in |
| // the navigation bar. |
| if (mFacetPackageMap.containsKey(packageName)) { |
| setCurrentFacet(mFacetPackageMap.get(packageName)); |
| } |
| |
| // Check if the package matches any of the categories for the facets |
| String category = getPackageCategory(packageName); |
| if (category != null) { |
| setCurrentFacet(mFacetCategoryMap.get(category)); |
| } |
| } |
| |
| private void bind() { |
| // Read up arrays_car.xml and populate the navigation bar here. |
| Resources r = mContext.getResources(); |
| TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons); |
| TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris); |
| TypedArray longpressIntents = |
| r.obtainTypedArray(R.array.car_facet_longpress_intent_uris); |
| TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters); |
| |
| TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters); |
| |
| if (icons.length() != intents.length() |
| || icons.length() != longpressIntents.length() |
| || icons.length() != facetPackageNames.length() |
| || icons.length() != facetCategories.length()) { |
| throw new RuntimeException("car_facet array lengths do not match"); |
| } |
| |
| mIntents = createEmptyIntentList(icons.length()); |
| mLongPressIntents = createEmptyIntentList(icons.length()); |
| |
| for (int i = 0; i < icons.length(); i++) { |
| Drawable icon = icons.getDrawable(i); |
| try { |
| mIntents.set(i, |
| Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME)); |
| |
| String longpressUri = longpressIntents.getString(i); |
| boolean hasLongpress = !longpressUri.isEmpty(); |
| if (hasLongpress) { |
| mLongPressIntents.set(i, |
| Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME)); |
| } |
| |
| CarNavigationButton button = createNavButton(icon, i, hasLongpress); |
| mNavButtons.add(button); |
| mNavBar.addButton(button, |
| createNavButton(icon, i, hasLongpress) /* lightsOutButton */); |
| |
| initFacetFilterMaps(i, |
| facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER), |
| facetCategories.getString(i).split(FACET_FILTER_DEMILITER)); |
| } catch (URISyntaxException e) { |
| throw new RuntimeException("Malformed intent uri", e); |
| } |
| } |
| } |
| |
| private void initFacetFilterMaps(int id, String[] packageNames, String[] categories) { |
| mFacetCategories.add(categories); |
| for (int i = 0; i < categories.length; i++) { |
| mFacetCategoryMap.put(categories[i], id); |
| } |
| |
| mFacetPackages.add(packageNames); |
| for (int i = 0; i < packageNames.length; i++) { |
| mFacetPackageMap.put(packageNames[i], id); |
| } |
| } |
| |
| private String getPackageCategory(String packageName) { |
| PackageManager pm = mContext.getPackageManager(); |
| int size = mFacetCategories.size(); |
| // For each facet, check if the given package name matches one of its categories |
| for (int i = 0; i < size; i++) { |
| String[] categories = mFacetCategories.get(i); |
| for (int j = 0; j < categories.length; j++) { |
| String category = categories[j]; |
| Intent intent = new Intent(); |
| intent.setPackage(packageName); |
| intent.setAction(Intent.ACTION_MAIN); |
| intent.addCategory(category); |
| List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); |
| if (list.size() > 0) { |
| // Cache this package name into facetPackageMap, so we won't have to query |
| // all categories next time this package name shows up. |
| mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category)); |
| return category; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Helper method to check if a given facet has multiple packages associated with it. |
| * This can be resource defined package names or package names filtered by facet category. |
| */ |
| private boolean facetHasMultiplePackages(int index) { |
| PackageManager pm = mContext.getPackageManager(); |
| |
| // Check if the packages defined for the filter actually exists on the device |
| String[] packages = mFacetPackages.get(index); |
| if (packages.length > 1) { |
| int count = 0; |
| for (int i = 0; i < packages.length; i++) { |
| count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0; |
| if (count > 1) { |
| return true; |
| } |
| } |
| } |
| |
| // If there weren't multiple packages defined for the facet, check the categories |
| // and see if they resolve to multiple package names |
| String categories[] = mFacetCategories.get(index); |
| |
| int count = 0; |
| for (int i = 0; i < categories.length; i++) { |
| String category = categories[i]; |
| Intent intent = new Intent(); |
| intent.setAction(Intent.ACTION_MAIN); |
| intent.addCategory(category); |
| count += pm.queryIntentActivities(intent, 0).size(); |
| if (count > 1) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void setCurrentFacet(int index) { |
| if (index == mCurrentFacetIndex) { |
| return; |
| } |
| |
| if (mNavButtons.get(mCurrentFacetIndex) != null) { |
| mNavButtons.get(mCurrentFacetIndex) |
| .setSelected(false /* selected */, false /* showMoreIcon */); |
| } |
| |
| if (mNavButtons.get(index) != null) { |
| mNavButtons.get(index).setSelected(true /* selected */, |
| facetHasMultiplePackages(index) /* showMoreIcon */); |
| } |
| mCurrentFacetIndex = index; |
| } |
| |
| private CarNavigationButton createNavButton(Drawable icon, final int id, |
| boolean longClickEnabled) { |
| CarNavigationButton button = (CarNavigationButton) View.inflate(mContext, |
| R.layout.car_navigation_button, null); |
| button.setResources(icon); |
| LinearLayout.LayoutParams lp = |
| new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1); |
| button.setLayoutParams(lp); |
| |
| button.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| onFacetClicked(id); |
| } |
| }); |
| |
| if (longClickEnabled) { |
| button.setLongClickable(true); |
| button.setOnLongClickListener(new View.OnLongClickListener() { |
| @Override |
| public boolean onLongClick(View v) { |
| onFacetLongClicked(id); |
| return true; |
| } |
| }); |
| } else { |
| button.setLongClickable(false); |
| } |
| |
| return button; |
| } |
| |
| private void startActivity(Intent intent) { |
| if (mActivityStarter != null && intent != null) { |
| mActivityStarter.startActivity(intent, true); |
| } |
| } |
| |
| private void onFacetClicked(int index) { |
| Intent intent = mIntents.get(index); |
| String packageName = intent.getPackage(); |
| |
| if (packageName == null) { |
| return; |
| } |
| |
| // Don't launch the lens picker if it's already running and the |
| // user clicks the same facet |
| if (packageName.equals(mCurrentPackageName) && index == mCurrentFacetIndex) { |
| return; |
| } |
| |
| intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories.get(index)); |
| intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages.get(index)); |
| // The facet is identified by the index in which it was added to the nav bar. |
| // This value can be used to determine which facet was selected |
| intent.putExtra(EXTRA_FACET_ID, Integer.toString(index)); |
| |
| // If the current facet is clicked, we want to launch the picker by default |
| // rather than the "preferred/last run" app. |
| intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex); |
| |
| setCurrentFacet(index); |
| startActivity(intent); |
| } |
| |
| private void onFacetLongClicked(int index) { |
| setCurrentFacet(index); |
| startActivity(mLongPressIntents.get(index)); |
| } |
| |
| private List<Intent> createEmptyIntentList(int size) { |
| return Arrays.asList(new Intent[size]); |
| } |
| } |