Merge branch 'bug_hunting' into develop
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / InstantUploadActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
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 package com.owncloud.android.ui.activity;
19
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import android.accounts.Account;
24 import android.app.Activity;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.os.Bundle;
30 import android.util.Log;
31 import android.util.SparseArray;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.view.View.OnLongClickListener;
36 import android.view.ViewGroup;
37 import android.widget.Button;
38 import android.widget.CheckBox;
39 import android.widget.CompoundButton;
40 import android.widget.CompoundButton.OnCheckedChangeListener;
41 import android.widget.ImageButton;
42 import android.widget.LinearLayout;
43 import android.widget.TextView;
44 import android.widget.Toast;
45
46 import com.owncloud.android.AccountUtils;
47 import com.owncloud.android.R;
48 import com.owncloud.android.db.DbHandler;
49 import com.owncloud.android.files.InstantUploadBroadcastReceiver;
50 import com.owncloud.android.files.services.FileUploader;
51 import com.owncloud.android.utils.FileStorageUtils;
52
53 /**
54 * This Activity is used to display a list with images they could not be
55 * uploaded instantly. The images can be selected for delete or for a try again
56 * upload
57 *
58 * The entry-point for this activity is the 'Failed upload Notification" and a
59 * sub-menu underneath the 'Upload' menu-item
60 *
61 * @author andomaex / Matthias Baumann
62 *
63 * This program is free software: you can redistribute it and/or modify
64 * it under the terms of the GNU General Public License as published by
65 * the Free Software Foundation, either version 3 of the License, or (at
66 * your option) any later version.
67 *
68 * This program is distributed in the hope that it will be useful, but
69 * WITHOUT ANY WARRANTY; without even the implied warranty of
70 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
71 * General Public License for more de/
72 */
73 public class InstantUploadActivity extends Activity {
74
75 private static final String LOG_TAG = InstantUploadActivity.class.getSimpleName();
76 private LinearLayout listView;
77 private static final String retry_chexbox_tag = "retry_chexbox_tag";
78 private static int MAX_LOAD_IMAGES = 5;
79 private int lastLoadImageIdx = 0;
80
81 private SparseArray<String> fileList = null;
82 CheckBox failed_upload_all_cb;
83
84 @Override
85 protected void onCreate(Bundle savedInstanceState) {
86 super.onCreate(savedInstanceState);
87 setContentView(R.layout.failed_upload_files);
88
89 Button delete_all_btn = (Button) findViewById(R.id.failed_upload_delete_all_btn);
90 delete_all_btn.setOnClickListener(getDeleteListner());
91 Button retry_all_btn = (Button) findViewById(R.id.failed_upload_retry_all_btn);
92 retry_all_btn.setOnClickListener(getRetryListner());
93 this.failed_upload_all_cb = (CheckBox) findViewById(R.id.failed_upload_headline_cb);
94 failed_upload_all_cb.setOnCheckedChangeListener(getCheckAllListener());
95 listView = (LinearLayout) findViewById(R.id.failed_upload_scrollviewlayout);
96
97 loadListView(true);
98
99 }
100
101 /**
102 * init the listview with ImageButtons, checkboxes and filename for every
103 * Image that was not successfully uploaded
104 *
105 * this method is call at Activity creation and on delete one ore more
106 * list-entry an on retry the upload by clicking the ImageButton or by click
107 * to the 'retry all' button
108 *
109 */
110 private void loadListView(boolean reset) {
111 DbHandler db = new DbHandler(getApplicationContext());
112 Cursor c = db.getFailedFiles();
113
114 if (reset) {
115 fileList = new SparseArray<String>();
116 listView.removeAllViews();
117 lastLoadImageIdx = 0;
118 }
119 if (c != null) {
120 try {
121 c.moveToPosition(lastLoadImageIdx);
122
123 while (c.moveToNext()) {
124
125 lastLoadImageIdx++;
126 String imp_path = c.getString(1);
127 String message = c.getString(4);
128 fileList.put(lastLoadImageIdx, imp_path);
129 LinearLayout rowLayout = getHorizontalLinearLayout(lastLoadImageIdx);
130 rowLayout.addView(getFileCheckbox(lastLoadImageIdx));
131 rowLayout.addView(getImageButton(imp_path, lastLoadImageIdx));
132 rowLayout.addView(getFileButton(imp_path, message, lastLoadImageIdx));
133 listView.addView(rowLayout);
134 Log.d(LOG_TAG, imp_path + " on idx: " + lastLoadImageIdx);
135 if (lastLoadImageIdx % MAX_LOAD_IMAGES == 0) {
136 break;
137 }
138 }
139 if (lastLoadImageIdx > 0) {
140 addLoadMoreButton(listView);
141 }
142 } finally {
143 db.close();
144 }
145 }
146 }
147
148 private void addLoadMoreButton(LinearLayout listView) {
149 if (listView != null) {
150 Button loadmoreBtn = null;
151 View oldButton = listView.findViewById(42);
152 if (oldButton != null) {
153 // remove existing button
154 listView.removeView(oldButton);
155 // to add the button at the end
156 loadmoreBtn = (Button) oldButton;
157 } else {
158 // create a new button to add to the scoll view
159 loadmoreBtn = new Button(this);
160 loadmoreBtn.setId(42);
161 loadmoreBtn.setText(getString(R.string.failed_upload_load_more_images));
162 loadmoreBtn.setBackgroundResource(R.color.owncloud_white);
163 loadmoreBtn.setTextSize(12);
164 loadmoreBtn.setOnClickListener(new OnClickListener() {
165 @Override
166 public void onClick(View v) {
167 loadListView(false);
168 }
169
170 });
171 }
172 listView.addView(loadmoreBtn);
173 }
174 }
175
176 /**
177 * provide a list of CheckBox instances, looked up from parent listview this
178 * list ist used to select/deselect all checkboxes at the list
179 *
180 * @return List<CheckBox>
181 */
182 private List<CheckBox> getCheckboxList() {
183 List<CheckBox> list = new ArrayList<CheckBox>();
184 for (int i = 0; i < listView.getChildCount(); i++) {
185 Log.d(LOG_TAG, "ListView has Childs: " + listView.getChildCount());
186 View childView = listView.getChildAt(i);
187 if (childView != null && childView instanceof ViewGroup) {
188 View checkboxView = getChildViews((ViewGroup) childView);
189 if (checkboxView != null && checkboxView instanceof CheckBox) {
190 Log.d(LOG_TAG, "found Child: " + checkboxView.getId() + " " + checkboxView.getClass());
191 list.add((CheckBox) checkboxView);
192 }
193 }
194 }
195 return list;
196 }
197
198 /**
199 * recursive called method, used from getCheckboxList method
200 *
201 * @param View
202 * @return View
203 */
204 private View getChildViews(ViewGroup view) {
205 if (view != null) {
206 for (int i = 0; i < view.getChildCount(); i++) {
207 View cb = view.getChildAt(i);
208 if (cb != null && cb instanceof ViewGroup) {
209 return getChildViews((ViewGroup) cb);
210 } else if (cb instanceof CheckBox) {
211 return cb;
212 }
213 }
214 }
215 return null;
216 }
217
218 /**
219 * create a new OnCheckedChangeListener for the 'check all' checkbox *
220 *
221 * @return OnCheckedChangeListener to select all checkboxes at the list
222 */
223 private OnCheckedChangeListener getCheckAllListener() {
224 return new OnCheckedChangeListener() {
225 @Override
226 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
227 List<CheckBox> list = getCheckboxList();
228 for (CheckBox checkbox : list) {
229 ((CheckBox) checkbox).setChecked(isChecked);
230 }
231 }
232
233 };
234 }
235
236 /**
237 * Button click Listener for the retry button at the headline
238 *
239 * @return a Listener to perform a retry for all selected images
240 */
241 private OnClickListener getRetryListner() {
242 return new OnClickListener() {
243
244 @Override
245 public void onClick(View v) {
246
247 try {
248
249 List<CheckBox> list = getCheckboxList();
250 for (CheckBox checkbox : list) {
251 boolean to_retry = checkbox.isChecked();
252
253 Log.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_retry);
254 String img_path = fileList.get(checkbox.getId());
255 if (to_retry) {
256
257 final String msg = "Image-Path " + checkbox.getId() + " was checked: " + img_path;
258 Log.d(LOG_TAG, msg);
259 startUpload(img_path);
260 }
261
262 }
263 } finally {
264 // refresh the List
265 listView.removeAllViews();
266 loadListView(true);
267 if (failed_upload_all_cb != null) {
268 failed_upload_all_cb.setChecked(false);
269 }
270 }
271
272 }
273 };
274 }
275
276 /**
277 * Button click Listener for the delete button at the headline
278 *
279 * @return a Listener to perform a delete for all selected images
280 */
281 private OnClickListener getDeleteListner() {
282
283 return new OnClickListener() {
284
285 @Override
286 public void onClick(View v) {
287
288 final DbHandler dbh = new DbHandler(getApplicationContext());
289 try {
290 List<CheckBox> list = getCheckboxList();
291 for (CheckBox checkbox : list) {
292 boolean to_be_delete = checkbox.isChecked();
293
294 Log.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_be_delete);
295 String img_path = fileList.get(checkbox.getId());
296 Log.d(LOG_TAG, "Image-Path " + checkbox.getId() + " was checked: " + img_path);
297 if (to_be_delete) {
298 boolean deleted = dbh.removeIUPendingFile(img_path);
299 Log.d(LOG_TAG, "removing " + checkbox.getId() + " was : " + deleted);
300
301 }
302
303 }
304 } finally {
305 dbh.close();
306 // refresh the List
307 listView.removeAllViews();
308 loadListView(true);
309 if (failed_upload_all_cb != null) {
310 failed_upload_all_cb.setChecked(false);
311 }
312 }
313
314 }
315 };
316 }
317
318 private LinearLayout getHorizontalLinearLayout(int id) {
319 LinearLayout linearLayout = new LinearLayout(getApplicationContext());
320 linearLayout.setId(id);
321 linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
322 LinearLayout.LayoutParams.MATCH_PARENT));
323 linearLayout.setGravity(Gravity.RIGHT);
324 linearLayout.setOrientation(LinearLayout.HORIZONTAL);
325 return linearLayout;
326 }
327
328 private LinearLayout getVerticalLinearLayout() {
329 LinearLayout linearLayout = new LinearLayout(getApplicationContext());
330 linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
331 LinearLayout.LayoutParams.MATCH_PARENT));
332 linearLayout.setGravity(Gravity.TOP);
333 linearLayout.setOrientation(LinearLayout.VERTICAL);
334 return linearLayout;
335 }
336
337 private View getFileButton(final String img_path, String message, int id) {
338
339 TextView failureTextView = new TextView(this);
340 failureTextView.setText(getString(R.string.failed_upload_failure_text) + message);
341 failureTextView.setBackgroundResource(R.color.owncloud_white);
342 failureTextView.setTextSize(8);
343 failureTextView.setOnLongClickListener(getOnLongClickListener(message));
344 failureTextView.setPadding(5, 5, 5, 10);
345 TextView retryButton = new TextView(this);
346 retryButton.setId(id);
347 retryButton.setText(img_path);
348 retryButton.setBackgroundResource(R.color.owncloud_white);
349 retryButton.setTextSize(8);
350 retryButton.setOnClickListener(getImageButtonOnClickListener(img_path));
351 retryButton.setOnLongClickListener(getOnLongClickListener(message));
352 retryButton.setPadding(5, 5, 5, 10);
353 LinearLayout verticalLayout = getVerticalLinearLayout();
354 verticalLayout.addView(retryButton);
355 verticalLayout.addView(failureTextView);
356
357 return verticalLayout;
358 }
359
360 private OnLongClickListener getOnLongClickListener(final String message) {
361 return new OnLongClickListener() {
362
363 @Override
364 public boolean onLongClick(View v) {
365 Log.d(LOG_TAG, message);
366 Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text)
367 + message, Toast.LENGTH_LONG);
368 toast.show();
369 return true;
370 }
371
372 };
373 }
374
375 private CheckBox getFileCheckbox(int id) {
376 CheckBox retryCB = new CheckBox(this);
377 retryCB.setId(id);
378 retryCB.setBackgroundResource(R.color.owncloud_white);
379 retryCB.setTextSize(8);
380 retryCB.setTag(retry_chexbox_tag);
381 return retryCB;
382 }
383
384 private ImageButton getImageButton(String img_path, int id) {
385 ImageButton imageButton = new ImageButton(this);
386 imageButton.setId(id);
387 imageButton.setClickable(true);
388 imageButton.setOnClickListener(getImageButtonOnClickListener(img_path));
389
390 // scale and add a thumbnail to the imagebutton
391 int base_scale_size = 32;
392 if (img_path != null) {
393 Log.d(LOG_TAG, "add " + img_path + " to Image Button");
394 BitmapFactory.Options options = new BitmapFactory.Options();
395 options.inJustDecodeBounds = true;
396 Bitmap bitmap = BitmapFactory.decodeFile(img_path, options);
397 int width_tpm = options.outWidth, height_tmp = options.outHeight;
398 int scale = 3;
399 while (true) {
400 if (width_tpm / 2 < base_scale_size || height_tmp / 2 < base_scale_size) {
401 break;
402 }
403 width_tpm /= 2;
404 height_tmp /= 2;
405 scale++;
406 }
407
408 Log.d(LOG_TAG, "scale Imgae with: " + scale);
409 BitmapFactory.Options options2 = new BitmapFactory.Options();
410 options2.inSampleSize = scale;
411 bitmap = BitmapFactory.decodeFile(img_path, options2);
412
413 if (bitmap != null) {
414 Log.d(LOG_TAG, "loaded Bitmap Bytes: " + bitmap.getRowBytes());
415 imageButton.setImageBitmap(bitmap);
416 } else {
417 Log.d(LOG_TAG, "could not load imgage: " + img_path);
418 }
419 }
420 return imageButton;
421 }
422
423 private OnClickListener getImageButtonOnClickListener(final String img_path) {
424 return new OnClickListener() {
425
426 @Override
427 public void onClick(View v) {
428 startUpload(img_path);
429 loadListView(true);
430 }
431
432 };
433 }
434
435 /**
436 * start uploading a file to the INSTANT_UPLOD_DIR
437 *
438 * @param img_path
439 */
440 private void startUpload(String img_path) {
441 // extract filename
442 String filename = FileStorageUtils.getInstantUploadFilePath(img_path);
443 if (canInstantUpload()) {
444 Account account = AccountUtils.getCurrentOwnCloudAccount(InstantUploadActivity.this);
445 // add file again to upload queue
446 DbHandler db = new DbHandler(InstantUploadActivity.this);
447 try {
448 db.updateFileState(img_path, DbHandler.UPLOAD_STATUS_UPLOAD_LATER, null);
449 } finally {
450 db.close();
451 }
452
453 Intent i = new Intent(InstantUploadActivity.this, FileUploader.class);
454 i.putExtra(FileUploader.KEY_ACCOUNT, account);
455 i.putExtra(FileUploader.KEY_LOCAL_FILE, img_path);
456 i.putExtra(FileUploader.KEY_REMOTE_FILE, filename);
457 i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
458 i.putExtra(com.owncloud.android.files.services.FileUploader.KEY_INSTANT_UPLOAD, true);
459
460 final String msg = "try to upload file with name :" + filename;
461 Log.d(LOG_TAG, msg);
462 Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text)
463 + filename, Toast.LENGTH_LONG);
464 toast.show();
465
466 startService(i);
467 } else {
468 Toast toast = Toast.makeText(InstantUploadActivity.this,
469 getString(R.string.failed_upload_retry_do_nothing_text) + filename, Toast.LENGTH_LONG);
470 toast.show();
471 }
472 }
473
474 private boolean canInstantUpload() {
475
476 if (!InstantUploadBroadcastReceiver.isOnline(this)
477 || (InstantUploadBroadcastReceiver.instantUploadViaWiFiOnly(this) && !InstantUploadBroadcastReceiver
478 .isConnectedViaWiFi(this))) {
479 return false;
480 } else {
481 return true;
482 }
483 }
484
485 }