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
onCreate(Bundle savedInstanceState)95     protected void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97 
98         updateListAndAdapter();
99     }
100 
101     @Override
onCreateOptionsMenu(Menu menu)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
onPrepareOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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 
ensureIntents()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      */
updateListAndAdapter()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 
updateAdapterToUseNewLists(final ArrayList<Map<String, ?>> newAdapterList, final ArrayList<ResolveInfo> newResolveList)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      */
fillResolveList(List<ResolveInfo> list)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 
createResolveAdapter(List<Map<String, ?>> list)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 
fillAdapterList(List<Map<String, ?>> list, List<ResolveInfo> resolveList)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. */
getResolveInfoTitle(ResolveInfo 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
onListItemClick(ListView l, View v, int position, long id)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 
getIntentForResolveInfo(ResolveInfo info, String action)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      */
startShortcutActivity(ResolveInfo info)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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
finish(Intent intent, String title)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      */
setViewValue(View view, Object data, String textRepresentation)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