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