ReceiveExternalFilesActivity.java 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author masensio
  6. * @author Juan Carlos González Cabrero
  7. * @author David A. Velasco
  8. * Copyright (C) 2012 Bartek Przybylski
  9. * Copyright (C) 2016 ownCloud Inc.
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License version 2,
  13. * as published by the Free Software Foundation.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. package com.owncloud.android.ui.activity;
  24. import android.accounts.Account;
  25. import android.accounts.AccountManager;
  26. import android.accounts.AuthenticatorException;
  27. import android.app.Dialog;
  28. import android.content.BroadcastReceiver;
  29. import android.content.Context;
  30. import android.content.DialogInterface;
  31. import android.content.DialogInterface.OnClickListener;
  32. import android.content.Intent;
  33. import android.content.IntentFilter;
  34. import android.content.res.Resources.NotFoundException;
  35. import android.graphics.drawable.Drawable;
  36. import android.os.Bundle;
  37. import android.os.Parcelable;
  38. import android.support.annotation.NonNull;
  39. import android.support.annotation.Nullable;
  40. import android.support.v4.app.DialogFragment;
  41. import android.support.v4.app.FragmentManager;
  42. import android.support.v4.content.ContextCompat;
  43. import android.support.v4.graphics.drawable.DrawableCompat;
  44. import android.support.v7.app.ActionBar;
  45. import android.support.v7.app.AlertDialog;
  46. import android.support.v7.app.AlertDialog.Builder;
  47. import android.text.format.DateFormat;
  48. import android.view.LayoutInflater;
  49. import android.view.Menu;
  50. import android.view.MenuInflater;
  51. import android.view.MenuItem;
  52. import android.view.View;
  53. import android.view.WindowManager.LayoutParams;
  54. import android.widget.AdapterView;
  55. import android.widget.AdapterView.OnItemClickListener;
  56. import android.widget.ArrayAdapter;
  57. import android.widget.Button;
  58. import android.widget.EditText;
  59. import android.widget.ListView;
  60. import android.widget.Spinner;
  61. import android.widget.TextView;
  62. import android.widget.Toast;
  63. import com.owncloud.android.MainApp;
  64. import com.owncloud.android.R;
  65. import com.owncloud.android.authentication.AccountAuthenticator;
  66. import com.owncloud.android.datamodel.OCFile;
  67. import com.owncloud.android.db.PreferenceManager;
  68. import com.owncloud.android.files.services.FileUploader;
  69. import com.owncloud.android.lib.common.operations.RemoteOperation;
  70. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  71. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  72. import com.owncloud.android.lib.common.utils.Log_OC;
  73. import com.owncloud.android.operations.CreateFolderOperation;
  74. import com.owncloud.android.operations.RefreshFolderOperation;
  75. import com.owncloud.android.operations.UploadFileOperation;
  76. import com.owncloud.android.syncadapter.FileSyncAdapter;
  77. import com.owncloud.android.ui.adapter.AccountListAdapter;
  78. import com.owncloud.android.ui.adapter.AccountListItem;
  79. import com.owncloud.android.ui.adapter.UploaderAdapter;
  80. import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
  81. import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
  82. import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
  83. import com.owncloud.android.ui.fragment.TaskRetainerFragment;
  84. import com.owncloud.android.ui.helpers.UriUploader;
  85. import com.owncloud.android.utils.DataHolderUtil;
  86. import com.owncloud.android.utils.ErrorMessageAdapter;
  87. import com.owncloud.android.utils.FileStorageUtils;
  88. import java.io.File;
  89. import java.io.FileWriter;
  90. import java.io.IOException;
  91. import java.io.UnsupportedEncodingException;
  92. import java.lang.reflect.Method;
  93. import java.util.ArrayList;
  94. import java.util.Calendar;
  95. import java.util.HashMap;
  96. import java.util.LinkedList;
  97. import java.util.List;
  98. import java.util.Stack;
  99. import java.util.Vector;
  100. /**
  101. * This can be used to upload things to an ownCloud instance.
  102. */
  103. public class ReceiveExternalFilesActivity extends FileActivity
  104. implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener {
  105. private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
  106. private static final String FTAG_ERROR_FRAGMENT = "ERROR_FRAGMENT";
  107. public static final String TEXT_FILE_SUFFIX = ".txt";
  108. public static final String URL_FILE_SUFFIX = ".url";
  109. public static final String WEBLOC_FILE_SUFFIX = ".webloc";
  110. public static final String DESKTOP_FILE_SUFFIX = ".desktop";
  111. private AccountManager mAccountManager;
  112. private Stack<String> mParents;
  113. private ArrayList<Parcelable> mStreamsToUpload;
  114. private String mUploadPath;
  115. private OCFile mFile;
  116. private SyncBroadcastReceiver mSyncBroadcastReceiver;
  117. private boolean mSyncInProgress = false;
  118. private boolean mAccountSelected;
  119. private boolean mAccountSelectionShowing;
  120. private final static int REQUEST_CODE__SETUP_ACCOUNT = REQUEST_CODE__LAST_SHARED + 1;
  121. private final static String KEY_PARENTS = "PARENTS";
  122. private final static String KEY_FILE = "FILE";
  123. private final static String KEY_ACCOUNT_SELECTED = "ACCOUNT_SELECTED";
  124. private final static String KEY_ACCOUNT_SELECTION_SHOWING = "ACCOUNT_SELECTION_SHOWING";
  125. private boolean mUploadFromTmpFile = false;
  126. private String mSubjectText;
  127. private String mExtraText;
  128. private final static String FILENAME_ENCODING = "UTF-8";
  129. @Override
  130. protected void onCreate(Bundle savedInstanceState) {
  131. prepareStreamsToUpload();
  132. if (savedInstanceState == null) {
  133. mParents = new Stack<>();
  134. mAccountSelected = false;
  135. mAccountSelectionShowing = false;
  136. } else {
  137. mParents = (Stack<String>) savedInstanceState.getSerializable(KEY_PARENTS);
  138. mFile = savedInstanceState.getParcelable(KEY_FILE);
  139. mAccountSelected = savedInstanceState.getBoolean(KEY_ACCOUNT_SELECTED);
  140. mAccountSelectionShowing = savedInstanceState.getBoolean(KEY_ACCOUNT_SELECTION_SHOWING);
  141. }
  142. super.onCreate(savedInstanceState);
  143. if (mAccountSelected) {
  144. setAccount((Account) savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT));
  145. }
  146. // Listen for sync messages
  147. IntentFilter syncIntentFilter = new IntentFilter(RefreshFolderOperation.
  148. EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
  149. syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
  150. mSyncBroadcastReceiver = new SyncBroadcastReceiver();
  151. registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
  152. // Init Fragment without UI to retain AsyncTask across configuration changes
  153. FragmentManager fm = getSupportFragmentManager();
  154. TaskRetainerFragment taskRetainerFragment =
  155. (TaskRetainerFragment) fm.findFragmentByTag(TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT);
  156. if (taskRetainerFragment == null) {
  157. taskRetainerFragment = new TaskRetainerFragment();
  158. fm.beginTransaction()
  159. .add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit();
  160. } // else, Fragment already created and retained across configuration change
  161. }
  162. @Override
  163. protected void setAccount(Account account, boolean savedAccount) {
  164. if (somethingToUpload()) {
  165. mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
  166. Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType());
  167. if (accounts.length == 0) {
  168. Log_OC.i(TAG, "No ownCloud account is available");
  169. DialogNoAccount dialog = new DialogNoAccount();
  170. dialog.show(getSupportFragmentManager(), null);
  171. } else if (accounts.length > 1 && !mAccountSelected && !mAccountSelectionShowing) {
  172. Log_OC.i(TAG, "More than one ownCloud is available");
  173. DialogMultipleAccount dialog = new DialogMultipleAccount();
  174. dialog.show(getSupportFragmentManager(), null);
  175. mAccountSelectionShowing = true;
  176. } else {
  177. if (!savedAccount) {
  178. setAccount(accounts[0]);
  179. }
  180. }
  181. } else {
  182. showErrorDialog(
  183. R.string.uploader_error_message_no_file_to_upload,
  184. R.string.uploader_error_title_no_file_to_upload
  185. );
  186. }
  187. super.setAccount(account, savedAccount);
  188. }
  189. @Override
  190. protected void onAccountSet(boolean stateWasRecovered) {
  191. super.onAccountSet(mAccountWasRestored);
  192. initTargetFolder();
  193. populateDirectoryList();
  194. }
  195. @Override
  196. protected void onSaveInstanceState(Bundle outState) {
  197. Log_OC.d(TAG, "onSaveInstanceState() start");
  198. super.onSaveInstanceState(outState);
  199. outState.putSerializable(KEY_PARENTS, mParents);
  200. outState.putParcelable(KEY_FILE, mFile);
  201. outState.putBoolean(KEY_ACCOUNT_SELECTED, mAccountSelected);
  202. outState.putBoolean(KEY_ACCOUNT_SELECTION_SHOWING, mAccountSelectionShowing);
  203. outState.putParcelable(FileActivity.EXTRA_ACCOUNT, getAccount());
  204. Log_OC.d(TAG, "onSaveInstanceState() end");
  205. }
  206. @Override
  207. protected void onDestroy(){
  208. if (mSyncBroadcastReceiver != null) {
  209. unregisterReceiver(mSyncBroadcastReceiver);
  210. }
  211. super.onDestroy();
  212. }
  213. public static class DialogNoAccount extends DialogFragment {
  214. @Override
  215. public Dialog onCreateDialog(Bundle savedInstanceState) {
  216. AlertDialog.Builder builder = new Builder(getActivity());
  217. builder.setIcon(R.drawable.ic_warning);
  218. builder.setTitle(R.string.uploader_wrn_no_account_title);
  219. builder.setMessage(String.format(
  220. getString(R.string.uploader_wrn_no_account_text),
  221. getString(R.string.app_name)));
  222. builder.setCancelable(false);
  223. builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
  224. @Override
  225. public void onClick(DialogInterface dialog, int which) {
  226. if (android.os.Build.VERSION.SDK_INT >
  227. android.os.Build.VERSION_CODES.ECLAIR_MR1) {
  228. // using string value since in API7 this
  229. // constant is not defined
  230. // in API7 < this constant is defined in
  231. // Settings.ADD_ACCOUNT_SETTINGS
  232. // and Settings.EXTRA_AUTHORITIES
  233. Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
  234. intent.putExtra("authorities", new String[]{MainApp.getAuthTokenType()});
  235. startActivityForResult(intent, REQUEST_CODE__SETUP_ACCOUNT);
  236. } else {
  237. // since in API7 there is no direct call for
  238. // account setup, so we need to
  239. // show our own AccountSetupActivity, get
  240. // desired results and setup
  241. // everything for ourselves
  242. Intent intent = new Intent(getActivity().getBaseContext(), AccountAuthenticator.class);
  243. startActivityForResult(intent, REQUEST_CODE__SETUP_ACCOUNT);
  244. }
  245. }
  246. });
  247. builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() {
  248. @Override
  249. public void onClick(DialogInterface dialog, int which) {
  250. getActivity().finish();
  251. }
  252. });
  253. return builder.create();
  254. }
  255. }
  256. public static class DialogMultipleAccount extends DialogFragment {
  257. private AccountListAdapter mAccountListAdapter;
  258. private Drawable mTintedCheck;
  259. @NonNull
  260. @Override
  261. public Dialog onCreateDialog(Bundle savedInstanceState) {
  262. final ReceiveExternalFilesActivity parent = (ReceiveExternalFilesActivity) getActivity();
  263. AlertDialog.Builder builder = new Builder(parent);
  264. mTintedCheck = DrawableCompat.wrap(ContextCompat.getDrawable(parent,
  265. R.drawable.ic_account_circle_white_18dp));
  266. int tint = ContextCompat.getColor(parent, R.color.primary);
  267. DrawableCompat.setTint(mTintedCheck, tint);
  268. mAccountListAdapter = new AccountListAdapter(parent, getAccountListItems(parent), mTintedCheck);
  269. builder.setTitle(R.string.common_choose_account);
  270. builder.setAdapter(mAccountListAdapter, new OnClickListener() {
  271. @Override
  272. public void onClick(DialogInterface dialog, int which) {
  273. final ReceiveExternalFilesActivity parent = (ReceiveExternalFilesActivity) getActivity();
  274. parent.setAccount(parent.mAccountManager.getAccountsByType(MainApp.getAccountType())[which]);
  275. parent.onAccountSet(parent.mAccountWasRestored);
  276. dialog.dismiss();
  277. parent.mAccountSelected = true;
  278. parent.mAccountSelectionShowing = false;
  279. }
  280. });
  281. builder.setCancelable(true);
  282. return builder.create();
  283. }
  284. /**
  285. * creates the account list items list including the add-account action in case multiaccount_support is enabled.
  286. *
  287. * @return list of account list items
  288. */
  289. private ArrayList<AccountListItem> getAccountListItems(ReceiveExternalFilesActivity activity) {
  290. Account[] accountList = activity.mAccountManager.getAccountsByType(MainApp.getAccountType());
  291. ArrayList<AccountListItem> adapterAccountList = new ArrayList<>(accountList.length);
  292. for (Account account : accountList) {
  293. adapterAccountList.add(new AccountListItem(account));
  294. }
  295. return adapterAccountList;
  296. }
  297. public void onCancel(DialogInterface dialog) {
  298. super.onCancel(dialog);
  299. final ReceiveExternalFilesActivity parent = (ReceiveExternalFilesActivity) getActivity();
  300. parent.mAccountSelectionShowing = false;
  301. parent.finish();
  302. }
  303. }
  304. public static class DialogInputUploadFilename extends DialogFragment {
  305. private static final String KEY_SUBJECT_TEXT = "SUBJECT_TEXT";
  306. private static final String KEY_EXTRA_TEXT = "EXTRA_TEXT";
  307. private static final int CATEGORY_URL = 1;
  308. private static final int CATEGORY_MAPS_URL = 2;
  309. private List<String> mFilenameBase;
  310. private List<String> mFilenameSuffix;
  311. private List<String> mText;
  312. private int mFileCategory;
  313. private Spinner mSpinner;
  314. public static DialogInputUploadFilename newInstance(String subjectText, String extraText) {
  315. DialogInputUploadFilename dialog = new DialogInputUploadFilename();
  316. Bundle args = new Bundle();
  317. args.putString(KEY_SUBJECT_TEXT, subjectText);
  318. args.putString(KEY_EXTRA_TEXT, extraText);
  319. dialog.setArguments(args);
  320. return dialog;
  321. }
  322. @NonNull
  323. @Override
  324. public Dialog onCreateDialog(Bundle savedInstanceState) {
  325. mFilenameBase = new ArrayList<>();
  326. mFilenameSuffix = new ArrayList<>();
  327. mText = new ArrayList<>();
  328. String subjectText = getArguments().getString(KEY_SUBJECT_TEXT);
  329. String extraText = getArguments().getString(KEY_EXTRA_TEXT);
  330. LayoutInflater layout = LayoutInflater.from(getActivity().getBaseContext());
  331. View view = layout.inflate(R.layout.upload_file_dialog, null);
  332. ArrayAdapter<String> adapter
  333. = new ArrayAdapter<>(getActivity().getBaseContext(), android.R.layout.simple_spinner_item);
  334. adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  335. int selectPos = 0;
  336. String filename = renameSafeFilename(subjectText);
  337. if (filename == null) {
  338. filename = "";
  339. }
  340. adapter.add(getString(R.string.upload_file_dialog_filetype_snippet_text));
  341. mText.add(extraText);
  342. mFilenameBase.add(filename);
  343. mFilenameSuffix.add(TEXT_FILE_SUFFIX);
  344. if (isIntentStartWithUrl(extraText)) {
  345. String str = getString(R.string.upload_file_dialog_filetype_internet_shortcut);
  346. mText.add(internetShortcutUrlText(extraText));
  347. mFilenameBase.add(filename);
  348. mFilenameSuffix.add(URL_FILE_SUFFIX);
  349. adapter.add(String.format(str,URL_FILE_SUFFIX));
  350. mText.add(internetShortcutWeblocText(extraText));
  351. mFilenameBase.add(filename);
  352. mFilenameSuffix.add(WEBLOC_FILE_SUFFIX);
  353. adapter.add(String.format(str,WEBLOC_FILE_SUFFIX));
  354. mText.add(internetShortcutDesktopText(extraText, filename));
  355. mFilenameBase.add(filename);
  356. mFilenameSuffix.add(DESKTOP_FILE_SUFFIX);
  357. adapter.add(String.format(str,DESKTOP_FILE_SUFFIX));
  358. selectPos = PreferenceManager.getUploadUrlFileExtensionUrlSelectedPos(getActivity());
  359. mFileCategory = CATEGORY_URL;
  360. } else if (isIntentFromGoogleMap(subjectText, extraText)) {
  361. String str = getString(R.string.upload_file_dialog_filetype_googlemap_shortcut);
  362. String texts[] = extraText.split("\n");
  363. mText.add(internetShortcutUrlText(texts[2]));
  364. mFilenameBase.add(texts[0]);
  365. mFilenameSuffix.add(URL_FILE_SUFFIX);
  366. adapter.add(String.format(str,URL_FILE_SUFFIX));
  367. mText.add(internetShortcutWeblocText(texts[2]));
  368. mFilenameBase.add(texts[0]);
  369. mFilenameSuffix.add(WEBLOC_FILE_SUFFIX);
  370. adapter.add(String.format(str,WEBLOC_FILE_SUFFIX));
  371. mText.add(internetShortcutDesktopText(texts[2], texts[0]));
  372. mFilenameBase.add(texts[0]);
  373. mFilenameSuffix.add(DESKTOP_FILE_SUFFIX);
  374. adapter.add(String.format(str,DESKTOP_FILE_SUFFIX));
  375. selectPos = PreferenceManager.getUploadMapFileExtensionUrlSelectedPos(getActivity());
  376. mFileCategory = CATEGORY_MAPS_URL;
  377. }
  378. final EditText userInput = (EditText) view.findViewById(R.id.user_input);
  379. setFilename(userInput, selectPos);
  380. userInput.requestFocus();
  381. final Spinner spinner = (Spinner) view.findViewById(R.id.file_type);
  382. setupSpinner(adapter, selectPos, userInput, spinner);
  383. if (adapter.getCount() == 1) {
  384. TextView label = (TextView) view.findViewById(R.id.label_file_type);
  385. label.setVisibility(View.GONE);
  386. spinner.setVisibility(View.GONE);
  387. }
  388. mSpinner = spinner;
  389. Dialog filenameDialog = createFilenameDialog(view, userInput, spinner);
  390. filenameDialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
  391. return filenameDialog;
  392. }
  393. private void setupSpinner(ArrayAdapter<String> adapter, int selectPos, final EditText userInput, Spinner spinner) {
  394. spinner.setAdapter(adapter);
  395. spinner.setSelection(selectPos, false);
  396. spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  397. @Override
  398. public void onItemSelected(AdapterView parent, View view, int position, long id) {
  399. Spinner spinner = (Spinner) parent;
  400. int selectPos = spinner.getSelectedItemPosition();
  401. setFilename(userInput, selectPos);
  402. saveSelection(selectPos);
  403. }
  404. @Override
  405. public void onNothingSelected(AdapterView<?> parent) {
  406. // nothing to do
  407. }
  408. });
  409. }
  410. @NonNull
  411. private Dialog createFilenameDialog(View view, final EditText userInput, final Spinner spinner) {
  412. Builder builder = new Builder(getActivity());
  413. builder.setView(view);
  414. builder.setTitle(R.string.upload_file_dialog_title);
  415. builder.setPositiveButton(R.string.common_ok, new OnClickListener() {
  416. public void onClick(DialogInterface dialog,int id) {
  417. int selectPos = spinner.getSelectedItemPosition();
  418. // verify if file name has suffix
  419. String filename = userInput.getText().toString();
  420. String suffix = mFilenameSuffix.get(selectPos);
  421. if (!filename.endsWith(suffix)){
  422. filename += suffix;
  423. }
  424. File file = createTempFile("tmp.tmp", mText.get(selectPos));
  425. if (file == null) {
  426. getActivity().finish();
  427. }
  428. String tmpname = file.getAbsolutePath();
  429. ((ReceiveExternalFilesActivity)getActivity()).uploadFile(tmpname, filename);
  430. }
  431. });
  432. builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
  433. public void onClick(DialogInterface dialog,int id) {
  434. dialog.cancel();
  435. }
  436. });
  437. return builder.create();
  438. }
  439. public void onPause() {
  440. hideSpinnerDropDown(mSpinner);
  441. super.onPause();
  442. }
  443. private void saveSelection(int selectPos) {
  444. switch (mFileCategory) {
  445. case CATEGORY_URL:
  446. PreferenceManager.setUploadUrlFileExtensionUrlSelectedPos(getActivity(), selectPos);
  447. break;
  448. case CATEGORY_MAPS_URL:
  449. PreferenceManager.setUploadMapFileExtensionUrlSelectedPos(getActivity(), selectPos);
  450. break;
  451. default:
  452. Log_OC.d(TAG, "Simple text snippet only: no selection to be persisted");
  453. break;
  454. }
  455. }
  456. private void hideSpinnerDropDown(Spinner spinner) {
  457. try {
  458. Method method = Spinner.class.getDeclaredMethod("onDetachedFromWindow");
  459. method.setAccessible(true);
  460. method.invoke(spinner);
  461. } catch (Exception e) {
  462. Log_OC.e(TAG, "onDetachedFromWindow", e);
  463. }
  464. }
  465. private void setFilename(EditText inputText, int selectPos)
  466. {
  467. String filename = mFilenameBase.get(selectPos) + mFilenameSuffix.get(selectPos);
  468. inputText.setText(filename);
  469. int selectionStart = 0;
  470. int extensionStart = filename.lastIndexOf(".");
  471. int selectionEnd = (extensionStart >= 0) ? extensionStart : filename.length();
  472. if (selectionEnd >= 0) {
  473. inputText.setSelection(
  474. Math.min(selectionStart, selectionEnd),
  475. Math.max(selectionStart, selectionEnd));
  476. }
  477. }
  478. private boolean isIntentFromGoogleMap(String subjectText, String extraText) {
  479. String texts[] = extraText.split("\n");
  480. if (texts.length != 3)
  481. return false;
  482. if (texts[0].length() == 0 || !subjectText.equals(texts[0]))
  483. return false;
  484. return texts[2].startsWith("https://goo.gl/maps/");
  485. }
  486. private boolean isIntentStartWithUrl(String extraText) {
  487. return (extraText.startsWith("http://") || extraText.startsWith("https://"));
  488. }
  489. @Nullable
  490. private String renameSafeFilename(String filename) {
  491. String safeFilename = filename;
  492. safeFilename = safeFilename.replaceAll("[?]", "_");
  493. safeFilename = safeFilename.replaceAll("\"", "_");
  494. safeFilename = safeFilename.replaceAll("/", "_");
  495. safeFilename = safeFilename.replaceAll("<", "_");
  496. safeFilename = safeFilename.replaceAll(">", "_");
  497. safeFilename = safeFilename.replaceAll("[*]", "_");
  498. safeFilename = safeFilename.replaceAll("[|]", "_");
  499. safeFilename = safeFilename.replaceAll(";", "_");
  500. safeFilename = safeFilename.replaceAll("=", "_");
  501. safeFilename = safeFilename.replaceAll(",", "_");
  502. try {
  503. int maxLength = 128;
  504. if (safeFilename.getBytes(FILENAME_ENCODING).length > maxLength) {
  505. safeFilename = new String(safeFilename.getBytes(FILENAME_ENCODING), 0, maxLength, FILENAME_ENCODING);
  506. }
  507. } catch (UnsupportedEncodingException e) {
  508. Log_OC.e(TAG, "rename failed ", e);
  509. return null;
  510. }
  511. return safeFilename;
  512. }
  513. private String internetShortcutUrlText(String url) {
  514. return "[InternetShortcut]\r\n" +
  515. "URL=" + url + "\r\n";
  516. }
  517. private String internetShortcutWeblocText(String url) {
  518. return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
  519. "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
  520. "<plist version=\"1.0\">\n" +
  521. "<dict>\n" +
  522. "<key>URL</key>\n" +
  523. "<string>" + url + "</string>\n" +
  524. "</dict>\n" +
  525. "</plist>\n";
  526. }
  527. private String internetShortcutDesktopText(String url, String filename) {
  528. return "[Desktop Entry]\n" +
  529. "Encoding=UTF-8\n" +
  530. "Name=" + filename + "\n" +
  531. "Type=Link\n" +
  532. "URL=" + url + "\n" +
  533. "Icon=text-html";
  534. }
  535. @Nullable
  536. private File createTempFile(String filename, String text) {
  537. File file = new File(getActivity().getCacheDir(), filename);
  538. FileWriter fw = null;
  539. try {
  540. fw = new FileWriter(file);
  541. fw.write(text);
  542. } catch (IOException e) {
  543. Log_OC.d(TAG, "Error ", e);
  544. return null;
  545. } finally {
  546. if (fw != null) {
  547. try {
  548. fw.close();
  549. } catch (IOException e) {
  550. Log_OC.d(TAG, "Error closing file writer ", e);
  551. }
  552. }
  553. }
  554. return file;
  555. }
  556. }
  557. @Override
  558. public void onBackPressed() {
  559. if (mParents.size() <= 1) {
  560. super.onBackPressed();
  561. } else {
  562. mParents.pop();
  563. String full_path = generatePath(mParents);
  564. startSyncFolderOperation(getStorageManager().getFileByPath(full_path));
  565. populateDirectoryList();
  566. }
  567. }
  568. @Override
  569. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  570. // click on folder in the list
  571. Log_OC.d(TAG, "on item click");
  572. Vector<OCFile> tmpfiles = getStorageManager().getFolderContent(mFile , false);
  573. sortFileList(tmpfiles);
  574. if (tmpfiles.size() <= 0) {
  575. return;
  576. }
  577. // filter on dirtype
  578. Vector<OCFile> files = new Vector<>();
  579. for (OCFile f : tmpfiles) {
  580. files.add(f);
  581. }
  582. if (files.size() < position) {
  583. throw new IndexOutOfBoundsException("Incorrect item selected");
  584. }
  585. if (files.get(position).isFolder()){
  586. OCFile folderToEnter = files.get(position);
  587. startSyncFolderOperation(folderToEnter);
  588. mParents.push(folderToEnter.getFileName());
  589. populateDirectoryList();
  590. }
  591. }
  592. @Override
  593. public void onClick(View v) {
  594. // click on button
  595. switch (v.getId()) {
  596. case R.id.uploader_choose_folder:
  597. mUploadPath = ""; // first element in mParents is root dir, represented by "";
  598. // init mUploadPath with "/" results in a "//" prefix
  599. for (String p : mParents) {
  600. mUploadPath += p + OCFile.PATH_SEPARATOR;
  601. }
  602. if (mUploadFromTmpFile){
  603. DialogInputUploadFilename dialog = DialogInputUploadFilename.newInstance(mSubjectText, mExtraText);
  604. dialog.show(getSupportFragmentManager(), null);
  605. } else {
  606. Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
  607. uploadFiles();
  608. }
  609. break;
  610. case R.id.uploader_cancel:
  611. finish();
  612. break;
  613. default:
  614. throw new IllegalArgumentException("Wrong element clicked");
  615. }
  616. }
  617. @Override
  618. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  619. super.onActivityResult(requestCode, resultCode, data);
  620. Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);
  621. if (requestCode == REQUEST_CODE__SETUP_ACCOUNT) {
  622. if (resultCode == RESULT_CANCELED) {
  623. finish();
  624. }
  625. Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType());
  626. if (accounts.length == 0) {
  627. DialogNoAccount dialog = new DialogNoAccount();
  628. dialog.show(getSupportFragmentManager(), null);
  629. } else {
  630. // there is no need for checking for is there more then one
  631. // account at this point
  632. // since account setup can set only one account at time
  633. setAccount(accounts[0]);
  634. populateDirectoryList();
  635. }
  636. }
  637. }
  638. private void populateDirectoryList() {
  639. setContentView(R.layout.uploader_layout);
  640. setupToolbar();
  641. ActionBar actionBar = getSupportActionBar();
  642. ListView mListView = (ListView) findViewById(android.R.id.list);
  643. String current_dir = mParents.peek();
  644. if ("".equals(current_dir)) {
  645. actionBar.setTitle(getString(R.string.uploader_top_message));
  646. } else {
  647. actionBar.setTitle(current_dir);
  648. }
  649. boolean notRoot = (mParents.size() > 1);
  650. actionBar.setDisplayHomeAsUpEnabled(notRoot);
  651. actionBar.setHomeButtonEnabled(notRoot);
  652. String full_path = generatePath(mParents);
  653. Log_OC.d(TAG, "Populating view with content of : " + full_path);
  654. mFile = getStorageManager().getFileByPath(full_path);
  655. if (mFile != null) {
  656. Vector<OCFile> files = getStorageManager().getFolderContent(mFile, false);
  657. sortFileList(files);
  658. List<HashMap<String, Object>> data = new LinkedList<>();
  659. for (OCFile f : files) {
  660. HashMap<String, Object> h = new HashMap<>();
  661. h.put("dirname", f);
  662. data.add(h);
  663. }
  664. UploaderAdapter sa = new UploaderAdapter(this,
  665. data,
  666. R.layout.uploader_list_item_layout,
  667. new String[] {"dirname"},
  668. new int[] {R.id.filename},
  669. getStorageManager(), getAccount());
  670. mListView.setAdapter(sa);
  671. Button btnChooseFolder = (Button) findViewById(R.id.uploader_choose_folder);
  672. btnChooseFolder.setOnClickListener(this);
  673. Button btnNewFolder = (Button) findViewById(R.id.uploader_cancel);
  674. btnNewFolder.setOnClickListener(this);
  675. mListView.setOnItemClickListener(this);
  676. }
  677. }
  678. @Override
  679. public void onSavedCertificate() {
  680. startSyncFolderOperation(getCurrentDir());
  681. }
  682. private void startSyncFolderOperation(OCFile folder) {
  683. long currentSyncTime = System.currentTimeMillis();
  684. mSyncInProgress = true;
  685. // perform folder synchronization
  686. RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
  687. currentSyncTime,
  688. false,
  689. false,
  690. false,
  691. getStorageManager(),
  692. getAccount(),
  693. getApplicationContext()
  694. );
  695. synchFolderOp.execute(getAccount(), this, null, null);
  696. }
  697. private Vector<OCFile> sortFileList(Vector<OCFile> files) {
  698. // Read sorting order, default to sort by name ascending
  699. FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(this);
  700. FileStorageUtils.mSortAscending = PreferenceManager.getSortAscending(this);
  701. return FileStorageUtils.sortOcFolder(files);
  702. }
  703. private String generatePath(Stack<String> dirs) {
  704. String full_path = "";
  705. for (String a : dirs) {
  706. full_path += a + "/";
  707. }
  708. return full_path;
  709. }
  710. private void prepareStreamsToUpload() {
  711. Intent intent = getIntent();
  712. if (intent.getAction().equals(Intent.ACTION_SEND)) {
  713. mStreamsToUpload = new ArrayList<>();
  714. mStreamsToUpload.add(intent.getParcelableExtra(Intent.EXTRA_STREAM));
  715. } else if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
  716. mStreamsToUpload = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
  717. }
  718. if (mStreamsToUpload == null || mStreamsToUpload.get(0) == null) {
  719. mStreamsToUpload = null;
  720. saveTextsFromIntent(intent);
  721. }
  722. }
  723. private void saveTextsFromIntent(Intent intent) {
  724. if (!intent.getType().equals("text/plain")) {
  725. return;
  726. }
  727. mUploadFromTmpFile = true;
  728. mSubjectText = intent.getStringExtra(Intent.EXTRA_SUBJECT);
  729. if (mSubjectText == null) {
  730. mSubjectText = intent.getStringExtra(Intent.EXTRA_TITLE);
  731. if (mSubjectText == null) {
  732. mSubjectText = DateFormat.format("yyyyMMdd_kkmmss", Calendar.getInstance()).toString();
  733. }
  734. }
  735. mExtraText = intent.getStringExtra(Intent.EXTRA_TEXT);
  736. }
  737. private boolean somethingToUpload() {
  738. return (mStreamsToUpload != null && mStreamsToUpload.size() > 0 && mStreamsToUpload.get(0) != null ||
  739. mUploadFromTmpFile);
  740. }
  741. public void uploadFile(String tmpname, String filename) {
  742. FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
  743. requester.uploadNewFile(
  744. getBaseContext(),
  745. getAccount(),
  746. tmpname,
  747. mFile.getRemotePath() + filename,
  748. FileUploader.LOCAL_BEHAVIOUR_COPY,
  749. null,
  750. true,
  751. UploadFileOperation.CREATED_BY_USER
  752. );
  753. finish();
  754. }
  755. public void uploadFiles() {
  756. UriUploader uploader = new UriUploader(
  757. this,
  758. mStreamsToUpload,
  759. mUploadPath,
  760. getAccount(),
  761. FileUploader.LOCAL_BEHAVIOUR_FORGET,
  762. true, // Show waiting dialog while file is being copied from private storage
  763. this // Copy temp task listener
  764. );
  765. UriUploader.UriUploaderResultCode resultCode = uploader.uploadUris();
  766. // Save the path to shared preferences; even if upload is not possible, user chose the folder
  767. PreferenceManager.setLastUploadPath(this, mUploadPath);
  768. if (resultCode == UriUploader.UriUploaderResultCode.OK) {
  769. finish();
  770. } else {
  771. int messageResTitle = R.string.uploader_error_title_file_cannot_be_uploaded;
  772. int messageResId = R.string.common_error_unknown;
  773. if (resultCode == UriUploader.UriUploaderResultCode.ERROR_NO_FILE_TO_UPLOAD) {
  774. messageResId = R.string.uploader_error_message_no_file_to_upload;
  775. messageResTitle = R.string.uploader_error_title_no_file_to_upload;
  776. } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_READ_PERMISSION_NOT_GRANTED) {
  777. messageResId = R.string.uploader_error_message_read_permission_not_granted;
  778. } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_UNKNOWN) {
  779. messageResId = R.string.common_error_unknown;
  780. }
  781. showErrorDialog(
  782. messageResId,
  783. messageResTitle
  784. );
  785. }
  786. }
  787. @Override
  788. public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
  789. super.onRemoteOperationFinish(operation, result);
  790. if (operation instanceof CreateFolderOperation) {
  791. onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
  792. }
  793. }
  794. /**
  795. * Updates the view associated to the activity after the finish of an operation
  796. * trying create a new folder
  797. *
  798. * @param operation Creation operation performed.
  799. * @param result Result of the creation.
  800. */
  801. private void onCreateFolderOperationFinish(CreateFolderOperation operation,
  802. RemoteOperationResult result) {
  803. if (result.isSuccess()) {
  804. String remotePath = operation.getRemotePath().substring(0, operation.getRemotePath().length() - 1);
  805. String newFolder = remotePath.substring(remotePath.lastIndexOf('/') + 1);
  806. mParents.push(newFolder);
  807. populateDirectoryList();
  808. } else {
  809. try {
  810. Toast msg = Toast.makeText(this,
  811. ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
  812. Toast.LENGTH_LONG);
  813. msg.show();
  814. } catch (NotFoundException e) {
  815. Log_OC.e(TAG, "Error while trying to show fail message ", e);
  816. }
  817. }
  818. }
  819. /**
  820. * Loads the target folder initialize shown to the user.
  821. * <p/>
  822. * The target account has to be chosen before this method is called.
  823. */
  824. private void initTargetFolder() {
  825. if (getStorageManager() == null) {
  826. throw new IllegalStateException("Do not call this method before " +
  827. "initializing mStorageManager");
  828. }
  829. String lastPath = PreferenceManager.getLastUploadPath(this);
  830. // "/" equals root-directory
  831. if ("/".equals(lastPath)) {
  832. mParents.add("");
  833. } else {
  834. String[] dir_names = lastPath.split("/");
  835. mParents.clear();
  836. for (String dir : dir_names) {
  837. mParents.add(dir);
  838. }
  839. }
  840. //Make sure that path still exists, if it doesn't pop the stack and try the previous path
  841. while (!getStorageManager().fileExists(generatePath(mParents)) && mParents.size() > 1) {
  842. mParents.pop();
  843. }
  844. }
  845. @Override
  846. public boolean onCreateOptionsMenu(Menu menu) {
  847. MenuInflater inflater = getMenuInflater();
  848. inflater.inflate(R.menu.main_menu, menu);
  849. menu.findItem(R.id.action_sort).setVisible(false);
  850. menu.findItem(R.id.action_switch_view).setVisible(false);
  851. menu.findItem(R.id.action_sync_account).setVisible(false);
  852. return true;
  853. }
  854. @Override
  855. public boolean onOptionsItemSelected(MenuItem item) {
  856. boolean retval = true;
  857. switch (item.getItemId()) {
  858. case R.id.action_create_dir:
  859. CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(mFile);
  860. dialog.show(
  861. getSupportFragmentManager(),
  862. CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT);
  863. break;
  864. case android.R.id.home:
  865. if ((mParents.size() > 1)) {
  866. onBackPressed();
  867. }
  868. break;
  869. default:
  870. retval = super.onOptionsItemSelected(item);
  871. }
  872. return retval;
  873. }
  874. private OCFile getCurrentFolder(){
  875. OCFile file = mFile;
  876. if (file != null) {
  877. if (file.isFolder()) {
  878. return file;
  879. } else if (getStorageManager() != null) {
  880. return getStorageManager().getFileByPath(file.getParentRemotePath());
  881. }
  882. }
  883. return null;
  884. }
  885. private void browseToRoot() {
  886. OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
  887. mFile = root;
  888. startSyncFolderOperation(root);
  889. }
  890. private class SyncBroadcastReceiver extends BroadcastReceiver {
  891. /**
  892. * {@link BroadcastReceiver} to enable syncing feedback in UI
  893. */
  894. @Override
  895. public void onReceive(Context context, Intent intent) {
  896. try {
  897. String event = intent.getAction();
  898. Log_OC.d(TAG, "Received broadcast " + event);
  899. String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
  900. String synchFolderRemotePath =
  901. intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
  902. RemoteOperationResult synchResult = (RemoteOperationResult)
  903. DataHolderUtil.getInstance().retrieve(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
  904. boolean sameAccount = (getAccount() != null &&
  905. accountName.equals(getAccount().name) && getStorageManager() != null);
  906. if (sameAccount) {
  907. if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
  908. mSyncInProgress = true;
  909. } else {
  910. OCFile currentFile = (mFile == null) ? null :
  911. getStorageManager().getFileByPath(mFile.getRemotePath());
  912. OCFile currentDir = (getCurrentFolder() == null) ? null :
  913. getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
  914. if (currentDir == null) {
  915. // current folder was removed from the server
  916. Toast.makeText(context,
  917. String.format(
  918. getString(R.string.sync_current_folder_was_removed),
  919. getCurrentFolder().getFileName()),
  920. Toast.LENGTH_LONG)
  921. .show();
  922. browseToRoot();
  923. } else {
  924. if (currentFile == null && !mFile.isFolder()) {
  925. // currently selected file was removed in the server, and now we know it
  926. currentFile = currentDir;
  927. }
  928. if (currentDir.getRemotePath().equals(synchFolderRemotePath)) {
  929. populateDirectoryList();
  930. }
  931. mFile = currentFile;
  932. }
  933. mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
  934. !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
  935. if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
  936. equals(event) &&
  937. /// TODO refactor and make common
  938. synchResult != null && !synchResult.isSuccess()) {
  939. if(synchResult.getCode() == ResultCode.UNAUTHORIZED ||
  940. (synchResult.isException() && synchResult.getException()
  941. instanceof AuthenticatorException)) {
  942. requestCredentialsUpdate(context);
  943. } else if(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED.equals(synchResult.getCode())) {
  944. showUntrustedCertDialog(synchResult);
  945. }
  946. }
  947. }
  948. removeStickyBroadcast(intent);
  949. Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
  950. }
  951. } catch (RuntimeException e) {
  952. // avoid app crashes after changing the serial id of RemoteOperationResult
  953. // in owncloud library with broadcast notifications pending to process
  954. removeStickyBroadcast(intent);
  955. DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
  956. }
  957. }
  958. }
  959. /**
  960. * Process the result of CopyAndUploadContentUrisTask
  961. */
  962. @Override
  963. public void onTmpFilesCopied(ResultCode result) {
  964. dismissLoadingDialog();
  965. finish();
  966. }
  967. /**
  968. * Show an error dialog, forcing the user to click a single button to exit the activity
  969. *
  970. * @param messageResId Resource id of the message to show in the dialog.
  971. * @param messageResTitle Resource id of the title to show in the dialog. 0 to show default alert message.
  972. * -1 to show no title.
  973. */
  974. private void showErrorDialog(int messageResId, int messageResTitle) {
  975. ConfirmationDialogFragment errorDialog = ConfirmationDialogFragment.newInstance(
  976. messageResId,
  977. new String[]{getString(R.string.app_name)}, // see uploader_error_message_* in strings.xml
  978. messageResTitle,
  979. R.string.common_back,
  980. -1,
  981. -1
  982. );
  983. errorDialog.setCancelable(false);
  984. errorDialog.setOnConfirmationListener(
  985. new ConfirmationDialogFragment.ConfirmationDialogFragmentListener() {
  986. @Override
  987. public void onConfirmation(String callerTag) {
  988. finish();
  989. }
  990. @Override
  991. public void onNeutral(String callerTag) {
  992. // not used at the moment
  993. }
  994. @Override
  995. public void onCancel(String callerTag) {
  996. // not used at the moment
  997. }
  998. }
  999. );
  1000. errorDialog.show(getSupportFragmentManager(), FTAG_ERROR_FRAGMENT);
  1001. }
  1002. }