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