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