Home | History | Annotate | Download | only in quicklaunch
      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 
     17 package com.android.settings.quicklaunch;
     18 
     19 import com.android.settings.R;
     20 
     21 import android.app.ListActivity;
     22 import android.content.Intent;
     23 import android.content.pm.ActivityInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.view.Menu;
     30 import android.view.MenuItem;
     31 import android.view.View;
     32 import android.widget.ImageView;
     33 import android.widget.ListView;
     34 import android.widget.SimpleAdapter;
     35 
     36 import java.util.ArrayList;
     37 import java.util.Collections;
     38 import java.util.List;
     39 import java.util.Map;
     40 import 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  */
     50 public 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