DirListActivity.java revision a0a586cd2fb06277436736abc291122aecf6c4ea
1/*
2 * Copyright (C) 2010 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.dumprendertree2.ui;
18
19import com.android.dumprendertree2.FileFilter;
20import com.android.dumprendertree2.TestsListActivity;
21import com.android.dumprendertree2.R;
22
23import android.app.Activity;
24import android.app.AlertDialog;
25import android.app.Dialog;
26import android.app.ListActivity;
27import android.app.ProgressDialog;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.res.Configuration;
31import android.os.Bundle;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.Message;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.AdapterView;
39import android.widget.ArrayAdapter;
40import android.widget.ImageView;
41import android.widget.ListView;
42import android.widget.TextView;
43
44import java.io.File;
45import java.util.ArrayList;
46import java.util.Collections;
47import java.util.List;
48
49/**
50 * An Activity that allows navigating through tests folders and choosing folders or tests to run.
51 */
52public class DirListActivity extends ListActivity {
53
54    private static final String LOG_TAG = "DirListActivity";
55    private static final String ROOT_DIR_PATH =
56            Environment.getExternalStorageDirectory() +
57            File.separator + "android" +
58            File.separator + "LayoutTests";
59
60    /** TODO: This is just a guess - think of a better way to achieve it */
61    private static final int MEAN_TITLE_CHAR_SIZE = 13;
62
63    private static final int PROGRESS_DIALOG_DELAY_MS = 200;
64
65    /** Code for the dialog, used in showDialog and onCreateDialog */
66    private static final int DIALOG_RUN_ABORT_DIR = 0;
67
68    /** Messages codes */
69    private static final int MSG_LOADED_ITEMS = 0;
70    private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
71
72    /** Initialized lazily before first sProgressDialog.show() */
73    private static ProgressDialog sProgressDialog;
74
75    private ListView mListView;
76
77    /** This is a relative path! */
78    private String mCurrentDirPath;
79
80    /**
81     * TODO: This should not be a constant, but rather be configurable from somewhere.
82     */
83    private String mRootDirPath = ROOT_DIR_PATH;
84
85    private FileFilter mFileFilter;
86
87    /**
88     * A thread responsible for loading the contents of the directory from sd card
89     * and sending them via Message to main thread that then loads them into
90     * ListView
91     */
92    private class LoadListItemsThread extends Thread {
93        private Handler mHandler;
94        private String mRelativePath;
95
96        public LoadListItemsThread(String relativePath, Handler handler) {
97            mRelativePath = relativePath;
98            mHandler = handler;
99        }
100
101        @Override
102        public void run() {
103            Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
104            msg.obj = getDirList(mRelativePath);
105            mHandler.sendMessage(msg);
106        }
107    }
108
109    /**
110     * Very simple object to use inside ListView as an item.
111     */
112    private static class ListItem implements Comparable<ListItem> {
113        private String mRelativePath;
114        private String mName;
115        private boolean mIsDirectory;
116
117        public ListItem(String relativePath, boolean isDirectory) {
118            mRelativePath = relativePath;
119            mName = new File(relativePath).getName();
120            mIsDirectory = isDirectory;
121        }
122
123        public boolean isDirectory() {
124            return mIsDirectory;
125        }
126
127        public String getRelativePath() {
128            return mRelativePath;
129        }
130
131        public String getName() {
132            return mName;
133        }
134
135        @Override
136        public int compareTo(ListItem another) {
137            return mRelativePath.compareTo(another.getRelativePath());
138        }
139
140        @Override
141        public boolean equals(Object o) {
142            if (!(o instanceof ListItem)) {
143                return false;
144            }
145
146            return mRelativePath.equals(((ListItem) o).getRelativePath());
147        }
148
149        @Override
150        public int hashCode() {
151            return mRelativePath.hashCode();
152        }
153
154    }
155
156    /**
157     * A custom adapter that sets the proper icon and label in the list view.
158     */
159    private static class DirListAdapter extends ArrayAdapter<ListItem> {
160        private Activity mContext;
161        private ListItem[] mItems;
162
163        public DirListAdapter(Activity context, ListItem[] items) {
164            super(context, R.layout.dirlist_row, items);
165
166            mContext = context;
167            mItems = items;
168        }
169
170        @Override
171        public View getView(int position, View convertView, ViewGroup parent) {
172            LayoutInflater inflater = mContext.getLayoutInflater();
173            View row = inflater.inflate(R.layout.dirlist_row, null);
174
175            TextView label = (TextView) row.findViewById(R.id.label);
176            label.setText(mItems[position].getName());
177
178            ImageView icon = (ImageView) row.findViewById(R.id.icon);
179            if (mItems[position].isDirectory()) {
180                icon.setImageResource(R.drawable.folder);
181            } else {
182                icon.setImageResource(R.drawable.runtest);
183            }
184
185            return row;
186        }
187    }
188
189    @Override
190    protected void onCreate(Bundle savedInstanceState) {
191        super.onCreate(savedInstanceState);
192
193        mFileFilter = new FileFilter(ROOT_DIR_PATH);
194        mListView = getListView();
195
196        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
197            @Override
198            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
199                ListItem item = (ListItem) parent.getItemAtPosition(position);
200
201                if (item.isDirectory()) {
202                    showDir(item.getRelativePath());
203                } else {
204                    /** Run the test */
205                    Intent intent = new Intent();
206                    intent.setClass(DirListActivity.this, TestsListActivity.class);
207                    intent.setAction(Intent.ACTION_RUN);
208                    intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, item.getRelativePath());
209                    startActivity(intent);
210                }
211            }
212        });
213
214        mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
215            @Override
216            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
217                ListItem item = (ListItem) parent.getItemAtPosition(position);
218
219                if (item.isDirectory()) {
220                    Bundle arguments = new Bundle(1);
221                    arguments.putString("name", item.getName());
222                    arguments.putString("relativePath", item.getRelativePath());
223                    showDialog(DIALOG_RUN_ABORT_DIR, arguments);
224                } else {
225                    /** TODO: Maybe show some info about a test? */
226                }
227
228                return true;
229            }
230        });
231
232        /** All the paths are relative to test root dir where possible */
233        showDir("");
234    }
235
236    @Override
237    /**
238     * Moves to the parent directory if one exists. Does not allow to move above
239     * the test 'root' directory.
240     */
241    public void onBackPressed() {
242        File currentDirParent = new File(mCurrentDirPath).getParentFile();
243        if (currentDirParent != null) {
244            showDir(currentDirParent.getPath());
245        } else {
246            showDir("");
247        }
248    }
249
250    /**
251     * Prevents the activity from recreating on change of orientation. The title needs to
252     * be recalculated.
253     */
254    @Override
255    public void onConfigurationChanged(Configuration newConfig) {
256        super.onConfigurationChanged(newConfig);
257
258        setTitle(shortenTitle(mCurrentDirPath));
259    }
260
261    @Override
262    protected Dialog onCreateDialog(int id, final Bundle args) {
263        Dialog dialog = null;
264        AlertDialog.Builder builder = new AlertDialog.Builder(this);
265
266        switch (id) {
267            case DIALOG_RUN_ABORT_DIR:
268                builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
269                        args.getString("name"));
270                builder.setMessage(R.string.dialog_run_abort_dir_msg);
271                builder.setCancelable(true);
272
273                builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
274                        new DialogInterface.OnClickListener() {
275                    @Override
276                    public void onClick(DialogInterface dialog, int which) {
277                        removeDialog(DIALOG_RUN_ABORT_DIR);
278                        /** Run the tests */
279                        Intent intent = new Intent();
280                        intent.setClass(DirListActivity.this, TestsListActivity.class);
281                        intent.setAction(Intent.ACTION_RUN);
282                        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH,
283                                args.getString("relativePath"));
284                        startActivity(intent);
285                    }
286                });
287
288                builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
289                        new DialogInterface.OnClickListener() {
290                    @Override
291                    public void onClick(DialogInterface dialog, int which) {
292                        removeDialog(DIALOG_RUN_ABORT_DIR);
293                    }
294                });
295
296                dialog = builder.create();
297                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
298                    @Override
299                    public void onCancel(DialogInterface dialog) {
300                        removeDialog(DIALOG_RUN_ABORT_DIR);
301                    }
302                });
303                break;
304        }
305
306        return dialog;
307    }
308
309    /**
310     * Loads the contents of dir into the list view.
311     *
312     * @param dirPath
313     *      directory to load into list view
314     */
315    private void showDir(String dirPath) {
316        mCurrentDirPath = dirPath;
317
318        /** Show progress dialog with a delay */
319        final Handler delayedDialogHandler = new Handler() {
320            @Override
321            public void handleMessage(Message msg) {
322                if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
323                    if (sProgressDialog == null) {
324                        sProgressDialog = new ProgressDialog(DirListActivity.this);
325                        sProgressDialog.setCancelable(false);
326                        sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
327                        sProgressDialog.setTitle(R.string.dialog_progress_title);
328                        sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
329                    }
330                    sProgressDialog.show();
331                }
332            }
333        };
334        Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
335        delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
336
337        /** Delegate loading contents from SD card to a new thread */
338        new LoadListItemsThread(mCurrentDirPath, new Handler() {
339            @Override
340            public void handleMessage(Message msg) {
341                if (msg.what == MSG_LOADED_ITEMS) {
342                    setListAdapter(new DirListAdapter(DirListActivity.this,
343                            (ListItem[])msg.obj));
344                    delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
345                    setTitle(shortenTitle(mCurrentDirPath));
346                    if (sProgressDialog != null) {
347                        sProgressDialog.dismiss();
348                    }
349                }
350            }
351        }).start();
352    }
353
354    /**
355     * TODO: find a neat way to determine number of characters that fit in the title
356     * bar.
357     * */
358    private String shortenTitle(String title) {
359        if (title.equals("")) {
360            return "Tests' root dir:";
361        }
362        int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
363
364        if (title.length() > charCount) {
365            return "..." + title.substring(title.length() - charCount);
366        } else {
367            return title;
368        }
369    }
370
371    /**
372     * Return the array with contents of the given directory.
373     * First it contains the subfolders, then the files. Both sorted
374     * alphabetically.
375     *
376     * The dirPath is relative.
377     */
378    private ListItem[] getDirList(String dirPath) {
379        File dir = new File(mRootDirPath, dirPath);
380
381        if (!dir.exists()) {
382            return new ListItem[0];
383        }
384
385        List<ListItem> subDirs = new ArrayList<ListItem>();
386        List<ListItem> subFiles = new ArrayList<ListItem>();
387
388        for (File item : dir.listFiles()) {
389            if (item.isDirectory() && FileFilter.isTestDir(item.getName())) {
390                subDirs.add(new ListItem(mFileFilter.getRelativePath(item), true));
391            } else if (FileFilter.isTestFile(item.getName())) {
392                subFiles.add(new ListItem(mFileFilter.getRelativePath(item), false));
393            }
394        }
395
396        Collections.sort(subDirs);
397        Collections.sort(subFiles);
398
399        /** Concatenate the two lists */
400        subDirs.addAll(subFiles);
401
402        return subDirs.toArray(new ListItem[subDirs.size()]);
403    }
404}