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}