Set or clear expiration date to protect public link in ShareFileFragment
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / ShareFileFragment.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author masensio
5 * @author David A. Velasco
6 * Copyright (C) 2015 ownCloud Inc.
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2,
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 package com.owncloud.android.ui.fragment;
23
24 import android.accounts.Account;
25 import android.app.Activity;
26 import android.graphics.Bitmap;
27 import android.os.Bundle;
28 import android.support.v4.app.Fragment;
29 import android.support.v7.widget.AppCompatButton;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.Button;
34 import android.widget.CompoundButton;
35 import android.widget.ImageView;
36 import android.widget.ListView;
37 import android.widget.Switch;
38 import android.widget.TextView;
39 import android.widget.Toast;
40
41 import com.owncloud.android.R;
42 import com.owncloud.android.authentication.AccountUtils;
43 import com.owncloud.android.datamodel.OCFile;
44 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
45 import com.owncloud.android.lib.common.utils.Log_OC;
46 import com.owncloud.android.lib.resources.shares.OCShare;
47 import com.owncloud.android.lib.resources.shares.ShareType;
48 import com.owncloud.android.ui.activity.FileActivity;
49 import com.owncloud.android.ui.adapter.ShareUserListAdapter;
50 import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
51 import com.owncloud.android.utils.DisplayUtils;
52 import com.owncloud.android.utils.MimetypeIconUtil;
53
54 import java.text.SimpleDateFormat;
55 import java.util.ArrayList;
56 import java.util.Date;
57
58 /**
59 * Fragment for Sharing a file with sharees (users or groups) or creating
60 * a public link.
61 *
62 * A simple {@link Fragment} subclass.
63 *
64 * Activities that contain this fragment must implement the
65 * {@link ShareFileFragment.OnShareFragmentInteractionListener} interface
66 * to handle interaction events.
67 *
68 * Use the {@link ShareFileFragment#newInstance} factory method to
69 * create an instance of this fragment.
70 */
71 public class ShareFileFragment extends Fragment
72 implements ShareUserListAdapter.ShareUserAdapterListener{
73
74 private static final String TAG = ShareFileFragment.class.getSimpleName();
75
76 // the fragment initialization parameters
77 private static final String ARG_FILE = "FILE";
78 private static final String ARG_ACCOUNT = "ACCOUNT";
79
80 /** File to share, received as a parameter in construction time */
81 private OCFile mFile;
82
83 /** OC account holding the file to share, received as a parameter in construction time */
84 private Account mAccount;
85
86 /** Reference to parent listener */
87 private OnShareFragmentInteractionListener mListener;
88
89 /** List of private shares bound to the file */
90 private ArrayList<OCShare> mPrivateShares;
91
92 /** Adapter to show private shares */
93 private ShareUserListAdapter mUserGroupsAdapter = null;
94
95 /** Public share bound to the file */
96 private OCShare mPublicShare;
97
98 /** Listener for changes on switch to share / unshare publicly */
99 private CompoundButton.OnCheckedChangeListener mOnShareViaLinkSwitchCheckedChangeListener;
100
101 /**
102 * Listener for changes on switch to set / clear password on public link
103 */
104 private CompoundButton.OnCheckedChangeListener mOnPasswordSwitchCheckedChangeListener;
105
106 /**
107 * Listener for changes on switch to set / clear expiration date on public link
108 */
109 private CompoundButton.OnCheckedChangeListener mOnExpirationDateSwitchCheckedChangeListener;
110
111
112 /**
113 * Public factory method to create new ShareFileFragment instances.
114 *
115 * @param fileToShare An {@link OCFile} to show in the fragment
116 * @param account An ownCloud account
117 * @return A new instance of fragment ShareFileFragment.
118 */
119 public static ShareFileFragment newInstance(OCFile fileToShare, Account account) {
120 ShareFileFragment fragment = new ShareFileFragment();
121 Bundle args = new Bundle();
122 args.putParcelable(ARG_FILE, fileToShare);
123 args.putParcelable(ARG_ACCOUNT, account);
124 fragment.setArguments(args);
125 return fragment;
126 }
127
128 public ShareFileFragment() {
129 // Required empty public constructor
130 }
131
132 /**
133 * {@inheritDoc}
134 */
135 @Override
136 public void onCreate(Bundle savedInstanceState) {
137 super.onCreate(savedInstanceState);
138 if (getArguments() != null) {
139 mFile = getArguments().getParcelable(ARG_FILE);
140 mAccount = getArguments().getParcelable(ARG_ACCOUNT);
141 }
142 }
143
144 /**
145 * {@inheritDoc}
146 */
147 @Override
148 public View onCreateView(LayoutInflater inflater, ViewGroup container,
149 Bundle savedInstanceState) {
150 // Inflate the layout for this fragment
151 View view = inflater.inflate(R.layout.share_file_layout, container, false);
152
153 // Setup layout
154 // Image
155 ImageView icon = (ImageView) view.findViewById(R.id.shareFileIcon);
156 icon.setImageResource(MimetypeIconUtil.getFileTypeIconId(mFile.getMimetype(),
157 mFile.getFileName()));
158 if (mFile.isImage()) {
159 String remoteId = String.valueOf(mFile.getRemoteId());
160 Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(remoteId);
161 if (thumbnail != null) {
162 icon.setImageBitmap(thumbnail);
163 }
164 }
165 // Name
166 TextView filename = (TextView) view.findViewById(R.id.shareFileName);
167 filename.setText(mFile.getFileName());
168 // Size
169 TextView size = (TextView) view.findViewById(R.id.shareFileSize);
170 if (mFile.isFolder()) {
171 size.setVisibility(View.GONE);
172 } else {
173 size.setText(DisplayUtils.bytesToHumanReadable(mFile.getFileLength()));
174 }
175
176 // Add User Button
177 Button addUserGroupButton = (Button)
178 view.findViewById(R.id.addUserButton);
179 addUserGroupButton.setOnClickListener(new View.OnClickListener() {
180 @Override
181 public void onClick(View view) {
182 boolean shareWithUsersEnable = AccountUtils.hasSearchUsersSupport(mAccount);
183 if (shareWithUsersEnable) {
184 // Show Search Fragment
185 mListener.showSearchUsersAndGroups();
186 } else {
187 String message = getString(R.string.share_sharee_unavailable);
188 Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
189 }
190 }
191 });
192
193 // Switch to create public share
194 mOnShareViaLinkSwitchCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
195 @Override
196 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
197 if (!isResumed()) {
198 // very important, setCheched(...) is called automatically during
199 // Fragment recreation on device rotations
200 return;
201 }
202 if (isChecked) {
203 ((FileActivity) getActivity()).getFileOperationsHelper().
204 shareFileViaLink(mFile);
205
206 } else {
207 ((FileActivity) getActivity()).getFileOperationsHelper().
208 unshareFileViaLink(mFile);
209 }
210 }
211 };
212 Switch shareViaLinkSwitch = (Switch) view.findViewById(R.id.shareViaLinkSectionSwitch);
213 shareViaLinkSwitch.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
214
215 // Switch for expiration date
216 mOnExpirationDateSwitchCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
217 @Override
218 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
219 if (!isResumed()) {
220 // very important, setCheched(...) is called automatically during
221 // Fragment recreation on device rotations
222 return;
223 }
224 if (isChecked) {
225 ExpirationDatePickerDialogFragment dialog =
226 ExpirationDatePickerDialogFragment.newInstance(mFile);
227 dialog.show(
228 getActivity().getSupportFragmentManager(),
229 ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
230 );
231
232 } else {
233 ((FileActivity) getActivity()).getFileOperationsHelper().
234 setExpirationDateToShareViaLink(mFile, -1, -1, -1);
235 }
236
237 // undo the toggle to grant the view will be correct if the dialog is cancelled
238 buttonView.setOnCheckedChangeListener(null);
239 buttonView.toggle();
240 buttonView.setOnCheckedChangeListener(mOnExpirationDateSwitchCheckedChangeListener);
241 }
242 };
243 Switch shareViaLinkExpirationSwitch = (Switch) view.findViewById(R.id.shareViaLinkExpirationSwitch);
244 shareViaLinkExpirationSwitch.setOnCheckedChangeListener(mOnExpirationDateSwitchCheckedChangeListener);
245
246 // Switch for password
247 mOnPasswordSwitchCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
248 @Override
249 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
250 if (!isResumed()) {
251 // very important, setCheched(...) is called automatically during
252 // Fragment recreation on device rotations
253 return;
254 }
255 if (isChecked) {
256 ((FileActivity) getActivity()).getFileOperationsHelper().
257 requestPasswordForShareViaLink(mFile);
258 } else {
259 ((FileActivity) getActivity()).getFileOperationsHelper().
260 setPasswordToShareViaLink(mFile, ""); // "" clears
261 }
262
263 // undo the toggle to grant the view will be correct if the dialog is cancelled
264 buttonView.setOnCheckedChangeListener(null);
265 buttonView.toggle();
266 buttonView.setOnCheckedChangeListener(mOnPasswordSwitchCheckedChangeListener);
267 }
268 };
269 Switch shareViaLinkPasswordSwitch = (Switch) view.findViewById(R.id.shareViaLinkPasswordSwitch);
270 shareViaLinkPasswordSwitch.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
271
272 return view;
273 }
274
275 @Override
276 public void onActivityCreated(Bundle savedInstanceState) {
277 super.onActivityCreated(savedInstanceState);
278
279 // Load data into the list of private shares
280 refreshUsersOrGroupsListFromDB();
281
282 // Load data of public share, if exists
283 refreshPublicShareFromDB();
284 }
285
286 @Override
287 public void onAttach(Activity activity) {
288 super.onAttach(activity);
289 try {
290 mListener = (OnShareFragmentInteractionListener) activity;
291 } catch (ClassCastException e) {
292 throw new ClassCastException(activity.toString()
293 + " must implement OnShareFragmentInteractionListener");
294 }
295 }
296
297 @Override
298 public void onDetach() {
299 super.onDetach();
300 mListener = null;
301 }
302
303 /**
304 * Get users and groups from the DB to fill in the "share with" list.
305 *
306 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
307 * instance ready to use. If not ready, does nothing.
308 */
309 public void refreshUsersOrGroupsListFromDB (){
310 if (((FileActivity) mListener).getStorageManager() != null) {
311 // Get Users and Groups
312 mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
313 mFile.getRemotePath(),
314 mAccount.name
315 );
316
317 // Update list of users/groups
318 updateListOfUserGroups();
319 }
320 }
321
322 private void updateListOfUserGroups() {
323 // Update list of users/groups
324 // TODO Refactoring: create a new {@link ShareUserListAdapter} instance with every call should not be needed
325 mUserGroupsAdapter = new ShareUserListAdapter(
326 getActivity(),
327 R.layout.share_user_item,
328 mPrivateShares,
329 this
330 );
331
332 // Show data
333 TextView noShares = (TextView) getView().findViewById(R.id.shareNoUsers);
334 ListView usersList = (ListView) getView().findViewById(R.id.shareUsersList);
335
336 if (mPrivateShares.size() > 0) {
337 noShares.setVisibility(View.GONE);
338 usersList.setVisibility(View.VISIBLE);
339 usersList.setAdapter(mUserGroupsAdapter);
340
341 } else {
342 noShares.setVisibility(View.VISIBLE);
343 usersList.setVisibility(View.GONE);
344 }
345 }
346
347 @Override
348 public void unshareButtonPressed(OCShare share) {
349 // Unshare
350 mListener.unshareWith(share);
351 Log_OC.d(TAG, "Unshare - " + share.getSharedWithDisplayName());
352 }
353
354
355
356 /**
357 * Get public link from the DB to fill in the "Share link" section in the UI.
358 *
359 * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
360 * instance ready to use. If not ready, does nothing.
361 */
362 public void refreshPublicShareFromDB() {
363 if (((FileActivity) mListener).getStorageManager() != null) {
364 // Get public share
365 mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
366 mFile.getRemotePath(),
367 ShareType.PUBLIC_LINK,
368 ""
369 );
370
371 // Update public share section
372 updatePublicShareSection();
373 }
374 }
375
376 /**
377 * Updates in the UI the section about public share with the information in the current
378 * public share bound to mFile, if any
379 */
380 private void updatePublicShareSection() {
381 if (mPublicShare != null && ShareType.PUBLIC_LINK.equals(mPublicShare.getShareType())) {
382 /// public share bound -> expand section
383 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
384 if (!shareViaLinkSwitch.isChecked()) {
385 // set null listener before setChecked() to prevent infinite loop of calls
386 shareViaLinkSwitch.setOnCheckedChangeListener(null);
387 shareViaLinkSwitch.setChecked(true);
388 shareViaLinkSwitch.setOnCheckedChangeListener(
389 mOnShareViaLinkSwitchCheckedChangeListener
390 );
391 }
392 getExpirationDateSection().setVisibility(View.VISIBLE);
393 getPasswordSection().setVisibility(View.VISIBLE);
394 getGetLinkButton().setVisibility(View.VISIBLE);
395
396 /// update state of expiration date switch and message depending on expiration date
397 /// update state of expiration date switch and message depending on expiration date
398 Switch expirationDateSwitch = getExpirationDateSwitch();
399 // set null listener before setChecked() to prevent infinite loop of calls
400 expirationDateSwitch.setOnCheckedChangeListener(null);
401 long expirationDate = mPublicShare.getExpirationDate();
402 if (expirationDate > 0) {
403 if (!expirationDateSwitch.isChecked()) {
404 expirationDateSwitch.toggle();
405 }
406 String formattedDate =
407 SimpleDateFormat.getDateInstance().format(
408 new Date(expirationDate)
409 );
410 getExpirationDateValue().setText(formattedDate);
411 } else {
412 if (expirationDateSwitch.isChecked()) {
413 expirationDateSwitch.toggle();
414 }
415 getExpirationDateValue().setText(R.string.empty);
416 }
417 // recover listener
418 expirationDateSwitch.setOnCheckedChangeListener(
419 mOnExpirationDateSwitchCheckedChangeListener
420 );
421
422 /// update state of password switch and message depending on password protection
423 Switch passwordSwitch = getPasswordSwitch();
424 // set null listener before setChecked() to prevent infinite loop of calls
425 passwordSwitch.setOnCheckedChangeListener(null);
426 if (mPublicShare.isPasswordProtected()) {
427 if (!passwordSwitch.isChecked()) {
428 passwordSwitch.toggle();
429 }
430 getPasswordValue().setVisibility(View.VISIBLE);
431 } else {
432 if (passwordSwitch.isChecked()) {
433 passwordSwitch.toggle();
434 }
435 getPasswordValue().setVisibility(View.INVISIBLE);
436 }
437 // recover listener
438 passwordSwitch.setOnCheckedChangeListener(
439 mOnPasswordSwitchCheckedChangeListener
440 );
441
442
443 } else {
444 /// no public share -> collapse section
445 Switch shareViaLinkSwitch = getShareViaLinkSwitch();
446 if (shareViaLinkSwitch.isChecked()) {
447 shareViaLinkSwitch.setOnCheckedChangeListener(null);
448 getShareViaLinkSwitch().setChecked(false);
449 shareViaLinkSwitch.setOnCheckedChangeListener(
450 mOnShareViaLinkSwitchCheckedChangeListener
451 );
452 }
453 getExpirationDateSection().setVisibility(View.GONE);
454 getPasswordSection().setVisibility(View.GONE);
455 getGetLinkButton().setVisibility(View.GONE);
456 }
457 }
458
459
460 /// BEWARE: next methods will failed with NullPointerException if called before onCreateView() finishes
461
462 private Switch getShareViaLinkSwitch() {
463 return (Switch) getView().findViewById(R.id.shareViaLinkSectionSwitch);
464 }
465
466 private View getExpirationDateSection() {
467 return getView().findViewById(R.id.shareViaLinkExpirationSection);
468 }
469
470 private Switch getExpirationDateSwitch() {
471 return (Switch) getView().findViewById(R.id.shareViaLinkExpirationSwitch);
472 }
473
474 private TextView getExpirationDateValue() {
475 return (TextView) getView().findViewById(R.id.shareViaLinkExpirationValue);
476 }
477
478 private View getPasswordSection() {
479 return getView().findViewById(R.id.shareViaLinkPasswordSection);
480 }
481
482 private Switch getPasswordSwitch() {
483 return (Switch) getView().findViewById(R.id.shareViaLinkPasswordSwitch);
484 }
485
486 private TextView getPasswordValue() {
487 return (TextView) getView().findViewById(R.id.shareViaLinkPasswordValue);
488 }
489
490 private AppCompatButton getGetLinkButton() {
491 return (AppCompatButton) getView().findViewById(R.id.shareViewLinkGetLinkButton);
492 }
493
494
495 /**
496 * This interface must be implemented by activities that contain this
497 * fragment to allow an interaction in this fragment to be communicated
498 * to the activity and potentially other fragments contained in that
499 * activity.
500 * <p/>
501 * See the Android Training lesson <a href=
502 * "http://developer.android.com/training/basics/fragments/communicating.html"
503 * >Communicating with Other Fragments</a> for more information.
504 */
505 public interface OnShareFragmentInteractionListener {
506 void showSearchUsersAndGroups();
507 void refreshUsersOrGroupsListFromServer();
508 void unshareWith(OCShare share);
509 }
510
511 }