1/* 2 * Copyright (C) 2007 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 */ 16 17package com.android.settings.quicklaunch; 18 19import com.android.settings.R; 20 21import android.app.ListActivity; 22import android.content.Intent; 23import android.content.pm.ActivityInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.ResolveInfo; 26import android.graphics.drawable.Drawable; 27import android.os.Bundle; 28import android.os.Handler; 29import android.view.Menu; 30import android.view.MenuItem; 31import android.view.View; 32import android.widget.ImageView; 33import android.widget.ListView; 34import android.widget.SimpleAdapter; 35 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.List; 39import java.util.Map; 40import java.util.TreeMap; 41 42/** 43 * Activity to pick a bookmark that will be returned to the caller. 44 * <p> 45 * Currently, bookmarks are either: 46 * <li> Activities that are in the launcher 47 * <li> Activities that are within an app that is capable of being launched with 48 * the {@link Intent#ACTION_CREATE_SHORTCUT}. 49 */ 50public class BookmarkPicker extends ListActivity implements SimpleAdapter.ViewBinder { 51 52 private static final String TAG = "BookmarkPicker"; 53 54 /** Extra in the returned intent from this activity. */ 55 public static final String EXTRA_TITLE = "com.android.settings.quicklaunch.TITLE"; 56 57 /** Extra that should be provided, and will be returned. */ 58 public static final String EXTRA_SHORTCUT = "com.android.settings.quicklaunch.SHORTCUT"; 59 60 /** 61 * The request code for the screen to create a bookmark that is WITHIN an 62 * application. For example, Gmail can return a bookmark for the inbox 63 * folder. 64 */ 65 private static final int REQUEST_CREATE_SHORTCUT = 1; 66 67 /** Intent used to get all the activities that are launch-able */ 68 private static Intent sLaunchIntent; 69 /** Intent used to get all the activities that are {@link #REQUEST_CREATE_SHORTCUT}-able */ 70 private static Intent sShortcutIntent; 71 72 /** 73 * List of ResolveInfo for activities that we can bookmark (either directly 74 * to the activity, or by launching the activity and it returning a bookmark 75 * WITHIN that application). 76 */ 77 private List<ResolveInfo> mResolveList; 78 79 // List adapter stuff 80 private static final String KEY_TITLE = "TITLE"; 81 private static final String KEY_RESOLVE_INFO = "RESOLVE_INFO"; 82 private static final String sKeys[] = new String[] { KEY_TITLE, KEY_RESOLVE_INFO }; 83 private static final int sResourceIds[] = new int[] { R.id.title, R.id.icon }; 84 private SimpleAdapter mMyAdapter; 85 86 /** Display those activities that are launch-able */ 87 private static final int DISPLAY_MODE_LAUNCH = 0; 88 /** Display those activities that are able to have bookmarks WITHIN the application */ 89 private static final int DISPLAY_MODE_SHORTCUT = 1; 90 private int mDisplayMode = DISPLAY_MODE_LAUNCH; 91 92 private Handler mUiHandler = new Handler(); 93 94 @Override 95 protected void onCreate(Bundle savedInstanceState) { 96 super.onCreate(savedInstanceState); 97 98 updateListAndAdapter(); 99 } 100 101 @Override 102 public boolean onCreateOptionsMenu(Menu menu) { 103 menu.add(0, DISPLAY_MODE_LAUNCH, 0, R.string.quick_launch_display_mode_applications) 104 .setIcon(com.android.internal.R.drawable.ic_menu_archive); 105 menu.add(0, DISPLAY_MODE_SHORTCUT, 0, R.string.quick_launch_display_mode_shortcuts) 106 .setIcon(com.android.internal.R.drawable.ic_menu_goto); 107 return true; 108 } 109 110 @Override 111 public boolean onPrepareOptionsMenu(Menu menu) { 112 menu.findItem(DISPLAY_MODE_LAUNCH).setVisible(mDisplayMode != DISPLAY_MODE_LAUNCH); 113 menu.findItem(DISPLAY_MODE_SHORTCUT).setVisible(mDisplayMode != DISPLAY_MODE_SHORTCUT); 114 return true; 115 } 116 117 @Override 118 public boolean onOptionsItemSelected(MenuItem item) { 119 120 switch (item.getItemId()) { 121 122 case DISPLAY_MODE_LAUNCH: 123 mDisplayMode = DISPLAY_MODE_LAUNCH; 124 break; 125 126 case DISPLAY_MODE_SHORTCUT: 127 mDisplayMode = DISPLAY_MODE_SHORTCUT; 128 break; 129 130 default: 131 return false; 132 } 133 134 updateListAndAdapter(); 135 return true; 136 } 137 138 private void ensureIntents() { 139 if (sLaunchIntent == null) { 140 sLaunchIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); 141 sShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 142 } 143 } 144 145 /** 146 * This should be called from the UI thread. 147 */ 148 private void updateListAndAdapter() { 149 // Get the activities in a separate thread 150 new Thread("data updater") { 151 @Override 152 public void run() { 153 synchronized (BookmarkPicker.this) { 154 /* 155 * Don't touch any of the lists that are being used by the 156 * adapter in this thread! 157 */ 158 ArrayList<ResolveInfo> newResolveList = new ArrayList<ResolveInfo>(); 159 ArrayList<Map<String, ?>> newAdapterList = new ArrayList<Map<String, ?>>(); 160 161 fillResolveList(newResolveList); 162 Collections.sort(newResolveList, 163 new ResolveInfo.DisplayNameComparator(getPackageManager())); 164 165 fillAdapterList(newAdapterList, newResolveList); 166 167 updateAdapterToUseNewLists(newAdapterList, newResolveList); 168 } 169 } 170 }.start(); 171 } 172 173 private void updateAdapterToUseNewLists(final ArrayList<Map<String, ?>> newAdapterList, 174 final ArrayList<ResolveInfo> newResolveList) { 175 // Post this back on the UI thread 176 mUiHandler.post(new Runnable() { 177 public void run() { 178 /* 179 * SimpleAdapter does not support changing the lists after it 180 * has been created. We just create a new instance. 181 */ 182 mMyAdapter = createResolveAdapter(newAdapterList); 183 mResolveList = newResolveList; 184 setListAdapter(mMyAdapter); 185 } 186 }); 187 } 188 189 /** 190 * Gets all activities matching our current display mode. 191 * 192 * @param list The list to fill. 193 */ 194 private void fillResolveList(List<ResolveInfo> list) { 195 ensureIntents(); 196 PackageManager pm = getPackageManager(); 197 list.clear(); 198 199 if (mDisplayMode == DISPLAY_MODE_LAUNCH) { 200 list.addAll(pm.queryIntentActivities(sLaunchIntent, 0)); 201 } else if (mDisplayMode == DISPLAY_MODE_SHORTCUT) { 202 list.addAll(pm.queryIntentActivities(sShortcutIntent, 0)); 203 } 204 } 205 206 private SimpleAdapter createResolveAdapter(List<Map<String, ?>> list) { 207 SimpleAdapter adapter = new SimpleAdapter(this, list, 208 R.layout.bookmark_picker_item, sKeys, sResourceIds); 209 adapter.setViewBinder(this); 210 return adapter; 211 } 212 213 private void fillAdapterList(List<Map<String, ?>> list, 214 List<ResolveInfo> resolveList) { 215 list.clear(); 216 int resolveListSize = resolveList.size(); 217 for (int i = 0; i < resolveListSize; i++) { 218 ResolveInfo info = resolveList.get(i); 219 /* 220 * Simple adapter craziness. For each item, we need to create a map 221 * from a key to its value (the value can be any object--the view 222 * binder will take care of filling the View with a representation 223 * of that object). 224 */ 225 Map<String, Object> map = new TreeMap<String, Object>(); 226 map.put(KEY_TITLE, getResolveInfoTitle(info)); 227 map.put(KEY_RESOLVE_INFO, info); 228 list.add(map); 229 } 230 } 231 232 /** Get the title for a resolve info. */ 233 private String getResolveInfoTitle(ResolveInfo info) { 234 CharSequence label = info.loadLabel(getPackageManager()); 235 if (label == null) label = info.activityInfo.name; 236 return label != null ? label.toString() : null; 237 } 238 239 @Override 240 protected void onListItemClick(ListView l, View v, int position, long id) { 241 if (position >= mResolveList.size()) return; 242 243 ResolveInfo info = mResolveList.get(position); 244 245 switch (mDisplayMode) { 246 247 case DISPLAY_MODE_LAUNCH: 248 // We can go ahead and return the clicked info's intent 249 Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN); 250 intent.addCategory(Intent.CATEGORY_LAUNCHER); 251 finish(intent, getResolveInfoTitle(info)); 252 break; 253 254 case DISPLAY_MODE_SHORTCUT: 255 // Start the shortcut activity so the user can pick the actual intent 256 // (example: Gmail's shortcut activity shows a list of mailboxes) 257 startShortcutActivity(info); 258 break; 259 } 260 261 } 262 263 private static Intent getIntentForResolveInfo(ResolveInfo info, String action) { 264 Intent intent = new Intent(action); 265 ActivityInfo ai = info.activityInfo; 266 intent.setClassName(ai.packageName, ai.name); 267 return intent; 268 } 269 270 /** 271 * Starts an activity to get a shortcut. 272 * <p> 273 * For example, Gmail has an activity that lists the available labels. It 274 * returns a shortcut intent for going directly to this label. 275 */ 276 private void startShortcutActivity(ResolveInfo info) { 277 Intent intent = getIntentForResolveInfo(info, Intent.ACTION_CREATE_SHORTCUT); 278 startActivityForResult(intent, REQUEST_CREATE_SHORTCUT); 279 280 // Will get a callback to onActivityResult 281 } 282 283 @Override 284 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 285 if (resultCode != RESULT_OK) { 286 return; 287 } 288 289 switch (requestCode) { 290 291 case REQUEST_CREATE_SHORTCUT: 292 if (data != null) { 293 finish((Intent) data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT), 294 data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)); 295 } 296 break; 297 298 default: 299 super.onActivityResult(requestCode, resultCode, data); 300 break; 301 } 302 } 303 304 /** 305 * Finishes the activity and returns the given data. 306 */ 307 private void finish(Intent intent, String title) { 308 // Give back what was given to us (it will have the shortcut, for example) 309 intent.putExtras(getIntent()); 310 // Put our information 311 intent.putExtra(EXTRA_TITLE, title); 312 setResult(RESULT_OK, intent); 313 finish(); 314 } 315 316 /** 317 * {@inheritDoc} 318 */ 319 public boolean setViewValue(View view, Object data, String textRepresentation) { 320 if (view.getId() == R.id.icon) { 321 Drawable icon = ((ResolveInfo) data).loadIcon(getPackageManager()); 322 if (icon != null) { 323 ((ImageView) view).setImageDrawable(icon); 324 } 325 return true; 326 } else { 327 return false; 328 } 329 } 330 331} 332