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