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