Workaround to show hidden accents in options menu
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / FileDisplayActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.ui.activity;
20
21 import java.io.File;
22
23 import android.accounts.Account;
24 import android.app.AlertDialog;
25 import android.app.ProgressDialog;
26 import android.app.AlertDialog.Builder;
27 import android.app.Dialog;
28 import android.content.BroadcastReceiver;
29 import android.content.ComponentName;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.DialogInterface.OnClickListener;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.ServiceConnection;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageInfo;
39 import android.content.pm.PackageManager.NameNotFoundException;
40 import android.content.res.Resources.NotFoundException;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.net.Uri;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.preference.PreferenceManager;
49 import android.provider.MediaStore;
50 import android.support.v4.app.FragmentTransaction;
51 import android.util.Log;
52 import android.view.View;
53 import android.view.ViewGroup;
54 import android.widget.ArrayAdapter;
55 import android.widget.EditText;
56 import android.widget.TextView;
57 import android.widget.Toast;
58
59 import com.actionbarsherlock.app.ActionBar;
60 import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
61 import com.actionbarsherlock.app.SherlockFragmentActivity;
62 import com.actionbarsherlock.view.Menu;
63 import com.actionbarsherlock.view.MenuInflater;
64 import com.actionbarsherlock.view.MenuItem;
65 import com.actionbarsherlock.view.Window;
66 import com.owncloud.android.AccountUtils;
67 import com.owncloud.android.authenticator.AccountAuthenticator;
68 import com.owncloud.android.datamodel.DataStorageManager;
69 import com.owncloud.android.datamodel.FileDataStorageManager;
70 import com.owncloud.android.datamodel.OCFile;
71 import com.owncloud.android.files.services.FileDownloader;
72 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
73 import com.owncloud.android.files.services.FileObserverService;
74 import com.owncloud.android.files.services.FileUploader;
75 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
76 import com.owncloud.android.network.OwnCloudClientUtils;
77 import com.owncloud.android.operations.RemoteOperationResult;
78 import com.owncloud.android.syncadapter.FileSyncService;
79 import com.owncloud.android.ui.dialog.SslValidatorDialog;
80 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
81 import com.owncloud.android.ui.fragment.FileDetailFragment;
82 import com.owncloud.android.ui.fragment.OCFileListFragment;
83
84 import com.owncloud.android.R;
85 import eu.alefzero.webdav.WebdavClient;
86
87 /**
88 * Displays, what files the user has available in his ownCloud.
89 *
90 * @author Bartek Przybylski
91 *
92 */
93
94 public class FileDisplayActivity extends SherlockFragmentActivity implements
95 OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener {
96
97 private ArrayAdapter<String> mDirectories;
98 private OCFile mCurrentDir = null;
99 private OCFile mCurrentFile = null;
100
101 private DataStorageManager mStorageManager;
102 private SyncBroadcastReceiver mSyncBroadcastReceiver;
103 private UploadFinishReceiver mUploadFinishReceiver;
104 private DownloadFinishReceiver mDownloadFinishReceiver;
105 private FileDownloaderBinder mDownloaderBinder = null;
106 private FileUploaderBinder mUploaderBinder = null;
107 private ServiceConnection mDownloadConnection = null, mUploadConnection = null;
108 private RemoteOperationResult mLastSslUntrustedServerResult = null;
109
110 private OCFileListFragment mFileList;
111
112 private boolean mDualPane;
113
114 private static final int DIALOG_SETUP_ACCOUNT = 0;
115 private static final int DIALOG_CREATE_DIR = 1;
116 private static final int DIALOG_ABOUT_APP = 2;
117 public static final int DIALOG_SHORT_WAIT = 3;
118 private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 4;
119 private static final int DIALOG_SSL_VALIDATOR = 5;
120 private static final int DIALOG_CERT_NOT_SAVED = 6;
121
122
123 private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1;
124 private static final int ACTION_SELECT_MULTIPLE_FILES = 2;
125
126 private static final String TAG = "FileDisplayActivity";
127
128 private static int[] mMenuIdentifiersToPatch = {R.id.about_app};
129
130 @Override
131 public void onCreate(Bundle savedInstanceState) {
132 Log.d(getClass().toString(), "onCreate() start");
133 super.onCreate(savedInstanceState);
134
135 /// Load of parameters from received intent
136 mCurrentDir = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); // no check necessary, mCurrenDir == null if the parameter is not in the intent
137 Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
138 if (account != null)
139 AccountUtils.setCurrentOwnCloudAccount(this, account.name);
140
141 /// Load of saved instance state: keep this always before initDataFromCurrentAccount()
142 if(savedInstanceState != null) {
143 // TODO - test if savedInstanceState should take precedence over file in the intent ALWAYS (now), NEVER, or SOME TIMES
144 mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);
145 }
146
147 if (!AccountUtils.accountsAreSetup(this)) {
148 /// no account available: FORCE ACCOUNT CREATION
149 mStorageManager = null;
150 createFirstAccount();
151
152 } else { /// at least an account is available
153
154 initDataFromCurrentAccount(); // it checks mCurrentDir and mCurrentFile with the current account
155
156 }
157
158 mUploadConnection = new ListServiceConnection();
159 mDownloadConnection = new ListServiceConnection();
160 bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);
161 bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);
162
163 // PIN CODE request ; best location is to decide, let's try this first
164 if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) {
165 requestPinCode();
166 }
167
168 // file observer
169 Intent observer_intent = new Intent(this, FileObserverService.class);
170 observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST);
171 startService(observer_intent);
172
173
174 /// USER INTERFACE
175 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
176
177 // Drop-down navigation
178 mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);
179 OCFile currFile = mCurrentDir;
180 while(currFile != null && currFile.getFileName() != OCFile.PATH_SEPARATOR) {
181 mDirectories.add(currFile.getFileName());
182 currFile = mStorageManager.getFileById(currFile.getParentId());
183 }
184 mDirectories.add(OCFile.PATH_SEPARATOR);
185
186 // Inflate and set the layout view
187 setContentView(R.layout.files);
188 mFileList = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
189 mDualPane = (findViewById(R.id.file_details_container) != null);
190 if (mDualPane) {
191 initFileDetailsInDualPane();
192 }
193
194 // Action bar setup
195 ActionBar actionBar = getSupportActionBar();
196 actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation
197 actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getParentId() != 0);
198 actionBar.setDisplayShowTitleEnabled(false);
199 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
200 actionBar.setListNavigationCallbacks(mDirectories, this);
201 setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to workaround bug in its implementation
202
203 Log.d(getClass().toString(), "onCreate() end");
204 }
205
206
207 /**
208 * Launches the account creation activity. To use when no ownCloud account is available
209 */
210 private void createFirstAccount() {
211 Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
212 intent.putExtra(android.provider.Settings.EXTRA_AUTHORITIES, new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE });
213 startActivity(intent); // the new activity won't be created until this.onStart() and this.onResume() are finished;
214 }
215
216
217 /**
218 * Load of state dependent of the existence of an ownCloud account
219 */
220 private void initDataFromCurrentAccount() {
221 /// Storage manager initialization - access to local database
222 mStorageManager = new FileDataStorageManager(
223 AccountUtils.getCurrentOwnCloudAccount(this),
224 getContentResolver());
225
226 /// Check if mCurrentDir is a directory
227 if(mCurrentDir != null && !mCurrentDir.isDirectory()) {
228 mCurrentFile = mCurrentDir;
229 mCurrentDir = mStorageManager.getFileById(mCurrentDir.getParentId());
230 }
231
232 /// Check if mCurrentDir and mCurrentFile are in the current account, and update them
233 if (mCurrentDir != null) {
234 mCurrentDir = mStorageManager.getFileByPath(mCurrentDir.getRemotePath()); // mCurrentDir == null if it is not in the current account
235 }
236 if (mCurrentFile != null) {
237 if (mCurrentFile.fileExists()) {
238 mCurrentFile = mStorageManager.getFileByPath(mCurrentFile.getRemotePath()); // mCurrentFile == null if it is not in the current account
239 } // else : keep mCurrentFile with the received value; this is currently the case of an upload in progress, when the user presses the status notification in a landscape tablet
240 }
241
242 /// Default to root if mCurrentDir was not found
243 if (mCurrentDir == null) {
244 mCurrentDir = mStorageManager.getFileByPath("/"); // will be NULL if the database was never synchronized
245 }
246 }
247
248
249 private void initFileDetailsInDualPane() {
250 if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) {
251 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
252 if (mCurrentFile != null) {
253 transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); // empty FileDetailFragment
254 mCurrentFile = null;
255 } else {
256 transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
257 }
258 transaction.commit();
259 }
260 }
261
262
263 @Override
264 public void onDestroy() {
265 super.onDestroy();
266 if (mDownloadConnection != null)
267 unbindService(mDownloadConnection);
268 if (mUploadConnection != null)
269 unbindService(mUploadConnection);
270 }
271
272
273 @Override
274 public boolean onCreateOptionsMenu(Menu menu) {
275 MenuInflater inflater = getSherlock().getMenuInflater();
276 inflater.inflate(R.menu.menu, menu);
277
278 patchHiddenAccents(menu);
279
280 return true;
281 }
282
283 /**
284 * Workaround for this: <a href="http://code.google.com/p/android/issues/detail?id=3974">http://code.google.com/p/android/issues/detail?id=3974</a>
285 *
286 * @param menu Menu to patch
287 */
288 private void patchHiddenAccents(Menu menu) {
289 for (int i = 0; i < mMenuIdentifiersToPatch.length ; i++) {
290 MenuItem aboutItem = menu.findItem(mMenuIdentifiersToPatch[i]);
291 if (aboutItem != null && aboutItem.getIcon() instanceof BitmapDrawable) {
292 // Clip off the bottom three (density independent) pixels of transparent padding
293 Bitmap original = ((BitmapDrawable) aboutItem.getIcon()).getBitmap();
294 float scale = getResources().getDisplayMetrics().density;
295 int clippedHeight = (int) (original.getHeight() - (3 * scale));
296 Bitmap scaled = Bitmap.createBitmap(original, 0, 0, original.getWidth(), clippedHeight);
297 aboutItem.setIcon(new BitmapDrawable(getResources(), scaled));
298 }
299 }
300 }
301
302
303 @Override
304 public boolean onOptionsItemSelected(MenuItem item) {
305 boolean retval = true;
306 switch (item.getItemId()) {
307 case R.id.createDirectoryItem: {
308 showDialog(DIALOG_CREATE_DIR);
309 break;
310 }
311 case R.id.startSync: {
312 startSynchronization();
313 break;
314 }
315 case R.id.action_upload: {
316 showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE);
317 break;
318 }
319 case R.id.action_settings: {
320 Intent settingsIntent = new Intent(this, Preferences.class);
321 startActivity(settingsIntent);
322 break;
323 }
324 case R.id.about_app : {
325 showDialog(DIALOG_ABOUT_APP);
326 break;
327 }
328 case android.R.id.home: {
329 if(mCurrentDir != null && mCurrentDir.getParentId() != 0){
330 onBackPressed();
331 }
332 break;
333 }
334 default:
335 retval = false;
336 }
337 return retval;
338 }
339
340 private void startSynchronization() {
341 ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE); // cancel the current synchronizations of any ownCloud account
342 Bundle bundle = new Bundle();
343 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
344 ContentResolver.requestSync(
345 AccountUtils.getCurrentOwnCloudAccount(this),
346 AccountAuthenticator.AUTH_TOKEN_TYPE, bundle);
347 }
348
349
350 @Override
351 public boolean onNavigationItemSelected(int itemPosition, long itemId) {
352 int i = itemPosition;
353 while (i-- != 0) {
354 onBackPressed();
355 }
356 // the next operation triggers a new call to this method, but it's necessary to
357 // ensure that the name exposed in the action bar is the current directory when the
358 // user selected it in the navigation list
359 if (itemPosition != 0)
360 getSupportActionBar().setSelectedNavigationItem(0);
361 return true;
362 }
363
364 /**
365 * Called, when the user selected something for uploading
366 */
367 public void onActivityResult(int requestCode, int resultCode, Intent data) {
368
369 if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && resultCode == RESULT_OK) {
370 requestSimpleUpload(data);
371
372 } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && resultCode == RESULT_OK) {
373 requestMultipleUpload(data);
374
375 }
376 }
377
378 private void requestMultipleUpload(Intent data) {
379 String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
380 if (filePaths != null) {
381 String[] remotePaths = new String[filePaths.length];
382 String remotePathBase = "";
383 for (int j = mDirectories.getCount() - 2; j >= 0; --j) {
384 remotePathBase += OCFile.PATH_SEPARATOR + mDirectories.getItem(j);
385 }
386 if (!remotePathBase.endsWith(OCFile.PATH_SEPARATOR))
387 remotePathBase += OCFile.PATH_SEPARATOR;
388 for (int j = 0; j< remotePaths.length; j++) {
389 remotePaths[j] = remotePathBase + (new File(filePaths[j])).getName();
390 }
391
392 Intent i = new Intent(this, FileUploader.class);
393 i.putExtra(FileUploader.KEY_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
394 i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);
395 i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
396 i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
397 startService(i);
398
399 } else {
400 Log.d("FileDisplay", "User clicked on 'Update' with no selection");
401 Toast t = Toast.makeText(this, getString(R.string.filedisplay_no_file_selected), Toast.LENGTH_LONG);
402 t.show();
403 return;
404 }
405 }
406
407
408 private void requestSimpleUpload(Intent data) {
409 String filepath = null;
410 try {
411 Uri selectedImageUri = data.getData();
412
413 String filemanagerstring = selectedImageUri.getPath();
414 String selectedImagePath = getPath(selectedImageUri);
415
416 if (selectedImagePath != null)
417 filepath = selectedImagePath;
418 else
419 filepath = filemanagerstring;
420
421 } catch (Exception e) {
422 Log.e("FileDisplay", "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e);
423 e.printStackTrace();
424
425 } finally {
426 if (filepath == null) {
427 Log.e("FileDisplay", "Couldnt resolve path to file");
428 Toast t = Toast.makeText(this, getString(R.string.filedisplay_unexpected_bad_get_content), Toast.LENGTH_LONG);
429 t.show();
430 return;
431 }
432 }
433
434 Intent i = new Intent(this, FileUploader.class);
435 i.putExtra(FileUploader.KEY_ACCOUNT,
436 AccountUtils.getCurrentOwnCloudAccount(this));
437 String remotepath = new String();
438 for (int j = mDirectories.getCount() - 2; j >= 0; --j) {
439 remotepath += OCFile.PATH_SEPARATOR + mDirectories.getItem(j);
440 }
441 if (!remotepath.endsWith(OCFile.PATH_SEPARATOR))
442 remotepath += OCFile.PATH_SEPARATOR;
443 remotepath += new File(filepath).getName();
444
445 i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath);
446 i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath);
447 i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
448 startService(i);
449 }
450
451
452 @Override
453 public void onBackPressed() {
454 if (mDirectories.getCount() <= 1) {
455 finish();
456 return;
457 }
458 popDirname();
459 mFileList.onNavigateUp();
460 mCurrentDir = mFileList.getCurrentFile();
461
462 if (mDualPane) {
463 // Resets the FileDetailsFragment on Tablets so that it always displays
464 FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
465 if (fileDetails != null && !fileDetails.isEmpty()) {
466 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
467 transaction.remove(fileDetails);
468 transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);
469 transaction.commit();
470 }
471 }
472
473 if(mCurrentDir.getParentId() == 0){
474 ActionBar actionBar = getSupportActionBar();
475 actionBar.setDisplayHomeAsUpEnabled(false);
476 }
477 }
478
479 @Override
480 protected void onSaveInstanceState(Bundle outState) {
481 // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved
482 Log.d(getClass().toString(), "onSaveInstanceState() start");
483 super.onSaveInstanceState(outState);
484 outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir);
485 if (mDualPane) {
486 FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
487 if (fragment != null) {
488 OCFile file = fragment.getDisplayedFile();
489 if (file != null) {
490 outState.putParcelable(FileDetailFragment.EXTRA_FILE, file);
491 }
492 }
493 }
494 Log.d(getClass().toString(), "onSaveInstanceState() end");
495 }
496
497 @Override
498 protected void onResume() {
499 Log.d(getClass().toString(), "onResume() start");
500 super.onResume();
501
502 if (AccountUtils.accountsAreSetup(this)) {
503
504 if (mStorageManager == null) {
505 // this is necessary for handling the come back to FileDisplayActivity when the first ownCloud account is created
506 initDataFromCurrentAccount();
507 if (mDualPane) {
508 initFileDetailsInDualPane();
509 }
510 }
511
512 // Listen for sync messages
513 IntentFilter syncIntentFilter = new IntentFilter(FileSyncService.SYNC_MESSAGE);
514 mSyncBroadcastReceiver = new SyncBroadcastReceiver();
515 registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
516
517 // Listen for upload messages
518 IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
519 mUploadFinishReceiver = new UploadFinishReceiver();
520 registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
521
522 // Listen for download messages
523 IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE);
524 mDownloadFinishReceiver = new DownloadFinishReceiver();
525 registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
526
527 // List current directory
528 mFileList.listDirectory(mCurrentDir); // TODO we should find the way to avoid the need of this (maybe it's not necessary yet; to check)
529
530 } else {
531
532 mStorageManager = null; // an invalid object will be there if all the ownCloud accounts are removed
533 showDialog(DIALOG_SETUP_ACCOUNT);
534
535 }
536 Log.d(getClass().toString(), "onResume() end");
537 }
538
539
540 @Override
541 protected void onPause() {
542 Log.d(getClass().toString(), "onPause() start");
543 super.onPause();
544 if (mSyncBroadcastReceiver != null) {
545 unregisterReceiver(mSyncBroadcastReceiver);
546 mSyncBroadcastReceiver = null;
547 }
548 if (mUploadFinishReceiver != null) {
549 unregisterReceiver(mUploadFinishReceiver);
550 mUploadFinishReceiver = null;
551 }
552 if (mDownloadFinishReceiver != null) {
553 unregisterReceiver(mDownloadFinishReceiver);
554 mDownloadFinishReceiver = null;
555 }
556 if (!AccountUtils.accountsAreSetup(this)) {
557 dismissDialog(DIALOG_SETUP_ACCOUNT);
558 }
559
560 Log.d(getClass().toString(), "onPause() end");
561 }
562
563
564 @Override
565 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
566 if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) {
567 ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);
568 }
569 }
570
571
572 @Override
573 protected Dialog onCreateDialog(int id) {
574 Dialog dialog = null;
575 AlertDialog.Builder builder;
576 switch (id) {
577 case DIALOG_SETUP_ACCOUNT: {
578 builder = new AlertDialog.Builder(this);
579 builder.setTitle(R.string.main_tit_accsetup);
580 builder.setMessage(R.string.main_wrn_accsetup);
581 builder.setCancelable(false);
582 builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
583 public void onClick(DialogInterface dialog, int which) {
584 createFirstAccount();
585 dialog.dismiss();
586 }
587 });
588 builder.setNegativeButton(R.string.common_exit, new OnClickListener() {
589 public void onClick(DialogInterface dialog, int which) {
590 dialog.dismiss();
591 finish();
592 }
593 });
594 //builder.setNegativeButton(android.R.string.cancel, this);
595 dialog = builder.create();
596 break;
597 }
598 case DIALOG_ABOUT_APP: {
599 builder = new AlertDialog.Builder(this);
600 builder.setTitle(getString(R.string.about_title));
601 PackageInfo pkg;
602 try {
603 pkg = getPackageManager().getPackageInfo(getPackageName(), 0);
604 builder.setMessage(String.format(getString(R.string.about_message), pkg.versionName));
605 builder.setIcon(android.R.drawable.ic_menu_info_details);
606 dialog = builder.create();
607 } catch (NameNotFoundException e) {
608 builder = null;
609 dialog = null;
610 Log.e(TAG, "Error while showing about dialog", e);
611 }
612 break;
613 }
614 case DIALOG_CREATE_DIR: {
615 builder = new Builder(this);
616 final EditText dirNameInput = new EditText(getBaseContext());
617 builder.setView(dirNameInput);
618 builder.setTitle(R.string.uploader_info_dirname);
619 int typed_color = getResources().getColor(R.color.setup_text_typed);
620 dirNameInput.setTextColor(typed_color);
621 builder.setPositiveButton(android.R.string.ok,
622 new OnClickListener() {
623 public void onClick(DialogInterface dialog, int which) {
624 String directoryName = dirNameInput.getText().toString();
625 if (directoryName.trim().length() == 0) {
626 dialog.cancel();
627 return;
628 }
629
630 // Figure out the path where the dir needs to be created
631 String path;
632 if (mCurrentDir == null) {
633 // this is just a patch; we should ensure that mCurrentDir never is null
634 if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) {
635 OCFile file = new OCFile(OCFile.PATH_SEPARATOR);
636 mStorageManager.saveFile(file);
637 }
638 mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
639 }
640 path = FileDisplayActivity.this.mCurrentDir.getRemotePath();
641
642 // Create directory
643 path += directoryName + OCFile.PATH_SEPARATOR;
644 Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler()));
645 thread.start();
646
647 dialog.dismiss();
648
649 showDialog(DIALOG_SHORT_WAIT);
650 }
651 });
652 builder.setNegativeButton(R.string.common_cancel,
653 new OnClickListener() {
654 public void onClick(DialogInterface dialog, int which) {
655 dialog.cancel();
656 }
657 });
658 dialog = builder.create();
659 break;
660 }
661 case DIALOG_SHORT_WAIT: {
662 ProgressDialog working_dialog = new ProgressDialog(this);
663 working_dialog.setMessage(getResources().getString(
664 R.string.wait_a_moment));
665 working_dialog.setIndeterminate(true);
666 working_dialog.setCancelable(false);
667 dialog = working_dialog;
668 break;
669 }
670 case DIALOG_CHOOSE_UPLOAD_SOURCE: {
671 final String [] items = { getString(R.string.actionbar_upload_files),
672 getString(R.string.actionbar_upload_from_apps) };
673 builder = new AlertDialog.Builder(this);
674 builder.setTitle(R.string.actionbar_upload);
675 builder.setItems(items, new DialogInterface.OnClickListener() {
676 public void onClick(DialogInterface dialog, int item) {
677 if (item == 0) {
678 //if (!mDualPane) {
679 Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);
680 startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);
681 //} else {
682 // TODO create and handle new fragment LocalFileListFragment
683 //}
684 } else if (item == 1) {
685 Intent action = new Intent(Intent.ACTION_GET_CONTENT);
686 action = action.setType("*/*")
687 .addCategory(Intent.CATEGORY_OPENABLE);
688 startActivityForResult(
689 Intent.createChooser(action, getString(R.string.upload_chooser_title)),
690 ACTION_SELECT_CONTENT_FROM_APPS);
691 }
692 }
693 });
694 dialog = builder.create();
695 break;
696 }
697 case DIALOG_SSL_VALIDATOR: {
698 dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);
699 break;
700 }
701 case DIALOG_CERT_NOT_SAVED: {
702 builder = new AlertDialog.Builder(this);
703 builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
704 builder.setCancelable(false);
705 builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
706 @Override
707 public void onClick(DialogInterface dialog, int which) {
708 dialog.dismiss();
709 };
710 });
711 dialog = builder.create();
712 break;
713 }
714 default:
715 dialog = null;
716 }
717
718 return dialog;
719 }
720
721
722 /**
723 * Translates a content URI of an image to a physical path
724 * on the disk
725 * @param uri The URI to resolve
726 * @return The path to the image or null if it could not be found
727 */
728 public String getPath(Uri uri) {
729 String[] projection = { MediaStore.Images.Media.DATA };
730 Cursor cursor = managedQuery(uri, projection, null, null, null);
731 if (cursor != null) {
732 int column_index = cursor
733 .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
734 cursor.moveToFirst();
735 return cursor.getString(column_index);
736 }
737 return null;
738 }
739
740 /**
741 * Pushes a directory to the drop down list
742 * @param directory to push
743 * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false.
744 */
745 public void pushDirname(OCFile directory) {
746 if(!directory.isDirectory()){
747 throw new IllegalArgumentException("Only directories may be pushed!");
748 }
749 mDirectories.insert(directory.getFileName(), 0);
750 mCurrentDir = directory;
751 }
752
753 /**
754 * Pops a directory name from the drop down list
755 * @return True, unless the stack is empty
756 */
757 public boolean popDirname() {
758 mDirectories.remove(mDirectories.getItem(0));
759 return !mDirectories.isEmpty();
760 }
761
762 private class DirectoryCreator implements Runnable {
763 private String mTargetPath;
764 private Account mAccount;
765 private Handler mHandler;
766
767 public DirectoryCreator(String targetPath, Account account, Handler handler) {
768 mTargetPath = targetPath;
769 mAccount = account;
770 mHandler = handler;
771 }
772
773 @Override
774 public void run() {
775 WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext());
776 boolean created = wdc.createDirectory(mTargetPath);
777 if (created) {
778 mHandler.post(new Runnable() {
779 @Override
780 public void run() {
781 dismissDialog(DIALOG_SHORT_WAIT);
782
783 // Save new directory in local database
784 OCFile newDir = new OCFile(mTargetPath);
785 newDir.setMimetype("DIR");
786 newDir.setParentId(mCurrentDir.getFileId());
787 mStorageManager.saveFile(newDir);
788
789 // Display the new folder right away
790 mFileList.listDirectory();
791 }
792 });
793
794 } else {
795 mHandler.post(new Runnable() {
796 @Override
797 public void run() {
798 dismissDialog(DIALOG_SHORT_WAIT);
799 try {
800 Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG);
801 msg.show();
802
803 } catch (NotFoundException e) {
804 Log.e(TAG, "Error while trying to show fail message " , e);
805 }
806 }
807 });
808 }
809 }
810
811 }
812
813 // Custom array adapter to override text colors
814 private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
815
816 public CustomArrayAdapter(FileDisplayActivity ctx, int view) {
817 super(ctx, view);
818 }
819
820 public View getView(int position, View convertView, ViewGroup parent) {
821 View v = super.getView(position, convertView, parent);
822
823 ((TextView) v).setTextColor(getResources().getColorStateList(
824 android.R.color.white));
825 return v;
826 }
827
828 public View getDropDownView(int position, View convertView,
829 ViewGroup parent) {
830 View v = super.getDropDownView(position, convertView, parent);
831
832 ((TextView) v).setTextColor(getResources().getColorStateList(
833 android.R.color.white));
834
835 return v;
836 }
837
838 }
839
840 private class SyncBroadcastReceiver extends BroadcastReceiver {
841
842 /**
843 * {@link BroadcastReceiver} to enable syncing feedback in UI
844 */
845 @Override
846 public void onReceive(Context context, Intent intent) {
847 boolean inProgress = intent.getBooleanExtra(
848 FileSyncService.IN_PROGRESS, false);
849 String accountName = intent
850 .getStringExtra(FileSyncService.ACCOUNT_NAME);
851
852 Log.d("FileDisplay", "sync of account " + accountName
853 + " is in_progress: " + inProgress);
854
855 if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)) {
856
857 String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH);
858
859 boolean fillBlankRoot = false;
860 if (mCurrentDir == null) {
861 mCurrentDir = mStorageManager.getFileByPath("/");
862 fillBlankRoot = (mCurrentDir != null);
863 }
864
865 if ((synchFolderRemotePath != null && mCurrentDir != null && (mCurrentDir.getRemotePath().equals(synchFolderRemotePath)))
866 || fillBlankRoot ) {
867 if (!fillBlankRoot)
868 mCurrentDir = getStorageManager().getFileByPath(synchFolderRemotePath);
869 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager()
870 .findFragmentById(R.id.fileList);
871 if (fileListFragment != null) {
872 fileListFragment.listDirectory(mCurrentDir);
873 }
874 }
875
876 setSupportProgressBarIndeterminateVisibility(inProgress);
877
878 }
879
880 RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT);
881 if (synchResult != null) {
882 if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {
883 mLastSslUntrustedServerResult = synchResult;
884 showDialog(DIALOG_SSL_VALIDATOR);
885 }
886 }
887 }
888 }
889
890
891 private class UploadFinishReceiver extends BroadcastReceiver {
892 /**
893 * Once the file upload has finished -> update view
894 * @author David A. Velasco
895 * {@link BroadcastReceiver} to enable upload feedback in UI
896 */
897 @Override
898 public void onReceive(Context context, Intent intent) {
899 long parentDirId = intent.getLongExtra(FileUploader.EXTRA_PARENT_DIR_ID, -1);
900 OCFile parentDir = mStorageManager.getFileById(parentDirId);
901 String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
902
903 if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
904 parentDir != null &&
905 ( (mCurrentDir == null && parentDir.getFileName().equals("/")) ||
906 parentDir.equals(mCurrentDir)
907 )
908 ) {
909 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
910 if (fileListFragment != null) {
911 fileListFragment.listDirectory();
912 }
913 }
914 }
915
916 }
917
918
919 /**
920 * Once the file download has finished -> update view
921 */
922 private class DownloadFinishReceiver extends BroadcastReceiver {
923 @Override
924 public void onReceive(Context context, Intent intent) {
925 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
926 String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
927
928 if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
929 mCurrentDir != null && mCurrentDir.getFileId() == mStorageManager.getFileByPath(downloadedRemotePath).getParentId()) {
930 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
931 if (fileListFragment != null) {
932 fileListFragment.listDirectory();
933 }
934 }
935 }
936 }
937
938
939
940
941 /**
942 * {@inheritDoc}
943 */
944 @Override
945 public DataStorageManager getStorageManager() {
946 return mStorageManager;
947 }
948
949
950 /**
951 * {@inheritDoc}
952 */
953 @Override
954 public void onDirectoryClick(OCFile directory) {
955 pushDirname(directory);
956 ActionBar actionBar = getSupportActionBar();
957 actionBar.setDisplayHomeAsUpEnabled(true);
958
959 if (mDualPane) {
960 // Resets the FileDetailsFragment on Tablets so that it always displays
961 FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
962 if (fileDetails != null && !fileDetails.isEmpty()) {
963 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
964 transaction.remove(fileDetails);
965 transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);
966 transaction.commit();
967 }
968 }
969 }
970
971
972 /**
973 * {@inheritDoc}
974 */
975 @Override
976 public void onFileClick(OCFile file) {
977
978 // If we are on a large device -> update fragment
979 if (mDualPane) {
980 // buttons in the details view are problematic when trying to reuse an existing fragment; create always a new one solves some of them, BUT no all; downloads are 'dangerous'
981 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
982 transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
983 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
984 transaction.commit();
985
986 } else { // small or medium screen device -> new Activity
987 Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
988 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
989 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
990 startActivity(showDetailsIntent);
991 }
992 }
993
994
995 /**
996 * {@inheritDoc}
997 */
998 @Override
999 public OCFile getInitialDirectory() {
1000 return mCurrentDir;
1001 }
1002
1003
1004 /**
1005 * {@inheritDoc}
1006 */
1007 @Override
1008 public void onFileStateChanged() {
1009 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
1010 if (fileListFragment != null) {
1011 fileListFragment.listDirectory();
1012 }
1013 }
1014
1015
1016 /**
1017 * {@inheritDoc}
1018 */
1019 @Override
1020 public FileDownloaderBinder getFileDownloaderBinder() {
1021 return mDownloaderBinder;
1022 }
1023
1024
1025 /**
1026 * {@inheritDoc}
1027 */
1028 @Override
1029 public FileUploaderBinder getFileUploaderBinder() {
1030 return mUploaderBinder;
1031 }
1032
1033
1034 /** Defines callbacks for service binding, passed to bindService() */
1035 private class ListServiceConnection implements ServiceConnection {
1036
1037 @Override
1038 public void onServiceConnected(ComponentName component, IBinder service) {
1039 if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {
1040 Log.d(TAG, "Download service connected");
1041 mDownloaderBinder = (FileDownloaderBinder) service;
1042 } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
1043 Log.d(TAG, "Upload service connected");
1044 mUploaderBinder = (FileUploaderBinder) service;
1045 } else {
1046 return;
1047 }
1048 // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS
1049 if (mFileList != null)
1050 mFileList.listDirectory();
1051 if (mDualPane) {
1052 FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
1053 if (fragment != null)
1054 fragment.updateFileDetails();
1055 }
1056 }
1057
1058 @Override
1059 public void onServiceDisconnected(ComponentName component) {
1060 if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {
1061 Log.d(TAG, "Download service disconnected");
1062 mDownloaderBinder = null;
1063 } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
1064 Log.d(TAG, "Upload service disconnected");
1065 mUploaderBinder = null;
1066 }
1067 }
1068 };
1069
1070
1071
1072 /**
1073 * Launch an intent to request the PIN code to the user before letting him use the app
1074 */
1075 private void requestPinCode() {
1076 boolean pinStart = false;
1077 SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
1078 pinStart = appPrefs.getBoolean("set_pincode", false);
1079 if (pinStart) {
1080 Intent i = new Intent(getApplicationContext(), PinCodeActivity.class);
1081 i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "FileDisplayActivity");
1082 startActivity(i);
1083 }
1084 }
1085
1086
1087 @Override
1088 public void onSavedCertificate() {
1089 startSynchronization();
1090 }
1091
1092
1093 @Override
1094 public void onFailedSavingCertificate() {
1095 showDialog(DIALOG_CERT_NOT_SAVED);
1096 }
1097
1098
1099 }