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