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