FileDocCategorySizeDatePackage
BookmarkPicker.javaAPI DocAndroid 1.5 API11969Wed May 06 22:42:48 BST 2009com.android.settings.quicklaunch

BookmarkPicker.java

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.quicklaunch;

import com.android.settings.R;

import android.app.ListActivity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Activity to pick a bookmark that will be returned to the caller.
 * <p>
 * Currently, bookmarks are either:
 * <li> Activities that are in the launcher
 * <li> Activities that are within an app that is capable of being launched with
 * the {@link Intent#ACTION_CREATE_SHORTCUT}.
 */
public class BookmarkPicker extends ListActivity implements SimpleAdapter.ViewBinder {

    private static final String TAG = "BookmarkPicker";

    /** Extra in the returned intent from this activity. */
    public static final String EXTRA_TITLE = "com.android.settings.quicklaunch.TITLE";
    
    /** Extra that should be provided, and will be returned. */
    public static final String EXTRA_SHORTCUT = "com.android.settings.quicklaunch.SHORTCUT";

    /**
     * The request code for the screen to create a bookmark that is WITHIN an
     * application. For example, Gmail can return a bookmark for the inbox
     * folder.
     */
    private static final int REQUEST_CREATE_SHORTCUT = 1;

    /** Intent used to get all the activities that are launch-able */
    private static Intent sLaunchIntent;
    /** Intent used to get all the activities that are {@link #REQUEST_CREATE_SHORTCUT}-able */
    private static Intent sShortcutIntent;
    
    /**
     * List of ResolveInfo for activities that we can bookmark (either directly
     * to the activity, or by launching the activity and it returning a bookmark
     * WITHIN that application).
     */
    private List<ResolveInfo> mResolveList;
    
    // List adapter stuff
    private static final String KEY_TITLE = "TITLE";
    private static final String KEY_RESOLVE_INFO = "RESOLVE_INFO";
    private static final String sKeys[] = new String[] { KEY_TITLE, KEY_RESOLVE_INFO };
    private static final int sResourceIds[] = new int[] { R.id.title, R.id.icon };
    private SimpleAdapter mMyAdapter;

    /** Display those activities that are launch-able */
    private static final int DISPLAY_MODE_LAUNCH = 0;
    /** Display those activities that are able to have bookmarks WITHIN the application */
    private static final int DISPLAY_MODE_SHORTCUT = 1;
    private int mDisplayMode = DISPLAY_MODE_LAUNCH;
    
    private Handler mUiHandler = new Handler();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        updateListAndAdapter();
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, DISPLAY_MODE_LAUNCH, 0, R.string.quick_launch_display_mode_applications)
                .setIcon(com.android.internal.R.drawable.ic_menu_archive);
        menu.add(0, DISPLAY_MODE_SHORTCUT, 0, R.string.quick_launch_display_mode_shortcuts)
                .setIcon(com.android.internal.R.drawable.ic_menu_goto);
        return true;
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(DISPLAY_MODE_LAUNCH).setVisible(mDisplayMode != DISPLAY_MODE_LAUNCH);
        menu.findItem(DISPLAY_MODE_SHORTCUT).setVisible(mDisplayMode != DISPLAY_MODE_SHORTCUT);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        
        switch (item.getItemId()) {

            case DISPLAY_MODE_LAUNCH: 
                mDisplayMode = DISPLAY_MODE_LAUNCH;
                break;
            
            case DISPLAY_MODE_SHORTCUT:
                mDisplayMode = DISPLAY_MODE_SHORTCUT;
                break;
            
            default:
                return false;
        }
        
        updateListAndAdapter();
        return true;
    }

    private void ensureIntents() {
        if (sLaunchIntent == null) {
            sLaunchIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
            sShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
        }
    }

    /**
     * This should be called from the UI thread.
     */
    private void updateListAndAdapter() {
        // Get the activities in a separate thread
        new Thread("data updater") {
            @Override
            public void run() {
                synchronized (BookmarkPicker.this) {
                    /*
                     * Don't touch any of the lists that are being used by the
                     * adapter in this thread!
                     */
                    ArrayList<ResolveInfo> newResolveList = new ArrayList<ResolveInfo>();
                    ArrayList<Map<String, ?>> newAdapterList = new ArrayList<Map<String, ?>>();

                    fillResolveList(newResolveList);
                    Collections.sort(newResolveList,
                            new ResolveInfo.DisplayNameComparator(getPackageManager()));
                    
                    fillAdapterList(newAdapterList, newResolveList);
                    
                    updateAdapterToUseNewLists(newAdapterList, newResolveList);
                }
            }
        }.start();  
    }
    
    private void updateAdapterToUseNewLists(final ArrayList<Map<String, ?>> newAdapterList,
            final ArrayList<ResolveInfo> newResolveList) {
        // Post this back on the UI thread
        mUiHandler.post(new Runnable() {
            public void run() {
                /*
                 * SimpleAdapter does not support changing the lists after it
                 * has been created. We just create a new instance.
                 */
                mMyAdapter = createResolveAdapter(newAdapterList);
                mResolveList = newResolveList;
                setListAdapter(mMyAdapter);
            }
        });
    }
    
    /**
     * Gets all activities matching our current display mode.
     * 
     * @param list The list to fill.
     */
    private void fillResolveList(List<ResolveInfo> list) {
        ensureIntents();
        PackageManager pm = getPackageManager();
        list.clear();
        
        if (mDisplayMode == DISPLAY_MODE_LAUNCH) {
            list.addAll(pm.queryIntentActivities(sLaunchIntent, 0));
        } else if (mDisplayMode == DISPLAY_MODE_SHORTCUT) {
            list.addAll(pm.queryIntentActivities(sShortcutIntent, 0)); 
        }
    }
    
    private SimpleAdapter createResolveAdapter(List<Map<String, ?>> list) {
        SimpleAdapter adapter = new SimpleAdapter(this, list,
                R.layout.bookmark_picker_item, sKeys, sResourceIds);
        adapter.setViewBinder(this);
        return adapter;
    }

    private void fillAdapterList(List<Map<String, ?>> list,
            List<ResolveInfo> resolveList) {
        list.clear();
        int resolveListSize = resolveList.size();
        for (int i = 0; i < resolveListSize; i++) {
            ResolveInfo info = resolveList.get(i);
            /*
             * Simple adapter craziness. For each item, we need to create a map
             * from a key to its value (the value can be any object--the view
             * binder will take care of filling the View with a representation
             * of that object).
             */
            Map<String, Object> map = new TreeMap<String, Object>();
            map.put(KEY_TITLE, getResolveInfoTitle(info));
            map.put(KEY_RESOLVE_INFO, info);
            list.add(map);
        }
    }

    /** Get the title for a resolve info. */
    private String getResolveInfoTitle(ResolveInfo info) {
        CharSequence label = info.loadLabel(getPackageManager());
        if (label == null) label = info.activityInfo.name;
        return label != null ? label.toString() : null;
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        if (position >= mResolveList.size()) return;

        ResolveInfo info = mResolveList.get(position);
        
        switch (mDisplayMode) {

            case DISPLAY_MODE_LAUNCH: 
                // We can go ahead and return the clicked info's intent
                Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN);
                intent.addCategory(Intent.CATEGORY_LAUNCHER);
                finish(intent, getResolveInfoTitle(info));
                break;

            case DISPLAY_MODE_SHORTCUT:
                // Start the shortcut activity so the user can pick the actual intent
                // (example: Gmail's shortcut activity shows a list of mailboxes)
                startShortcutActivity(info);
                break;
        }
        
    }
    
    private static Intent getIntentForResolveInfo(ResolveInfo info, String action) {
        Intent intent = new Intent(action);
        ActivityInfo ai = info.activityInfo;
        intent.setClassName(ai.packageName, ai.name);
        return intent;
    }

    /**
     * Starts an activity to get a shortcut.
     * <p>
     * For example, Gmail has an activity that lists the available labels. It
     * returns a shortcut intent for going directly to this label.
     */
    private void startShortcutActivity(ResolveInfo info) {
        Intent intent = getIntentForResolveInfo(info, Intent.ACTION_CREATE_SHORTCUT);
        startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
        
        // Will get a callback to onActivityResult
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            return;
        }
        
        switch (requestCode) {
            
            case REQUEST_CREATE_SHORTCUT:
                if (data != null) {
                    finish((Intent) data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT),
                            data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME));
                }
                break;
                
            default:
                super.onActivityResult(requestCode, resultCode, data);
                break;
        }
    }
    
    /**
     * Finishes the activity and returns the given data.
     */
    private void finish(Intent intent, String title) {
        // Give back what was given to us (it will have the shortcut, for example)
        intent.putExtras(getIntent());
        // Put our information
        intent.putExtra(EXTRA_TITLE, title);
        setResult(RESULT_OK, intent);
        finish();
    }

    /**
     * {@inheritDoc}
     */
    public boolean setViewValue(View view, Object data, String textRepresentation) {
        if (view.getId() == R.id.icon) {
            Drawable icon = ((ResolveInfo) data).loadIcon(getPackageManager());
            if (icon != null) {
                ((ImageView) view).setImageDrawable(icon);
            }
            return true;
        } else {
            return false;
        }
    }
    
}