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