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