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