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}