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