<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />\r
<uses-permission android:name="android.permission.READ_PHONE_STATE" />\r
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>\r
-\r
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>\r
+ \r
<uses-sdk\r
android:minSdkVersion="8"\r
android:targetSdkVersion="13" />\r
</activity>\r
<activity android:name=".ui.activity.UploadFilesActivity">\r
</activity>\r
+ <activity android:name=".ui.activity.InstantUploadActivity">\r
+ </activity>\r
+ <activity android:name=".ui.activity.FailedUploadActivity" android:theme="@android:style/Theme.Dialog" android:excludeFromRecents="true"/>\r
<activity android:name=".Uploader" >\r
<intent-filter>\r
<action android:name="android.intent.action.SEND" >\r
</activity>\r
<activity android:name=".ui.activity.PreferencesNewSessionewSession" >\r
</activity>\r
+ \r
+ <activity android:name="com.owncloud.android.ui.preview.PreviewImageActivity" />\r
+ \r
+ <activity android:name="com.owncloud.android.ui.preview.PreviewVideoActivity"\r
+ android:label="@string/app_name"\r
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >\r
+ </activity> \r
\r
<service\r
android:name=".authenticator.AccountAuthenticatorService"\r
</intent-filter>\r
</activity>\r
\r
- <service android:name=".files.services.FileDownloader" >\r
- </service>\r
+ <service android:name=".files.services.FileDownloader" />\r
+ <service android:name=".files.services.FileUploader" />\r
+ <service android:name=".media.MediaService" />\r
\r
- <activity android:name=".ui.activity.FileDetailActivity" />
+ <activity android:name=".ui.activity.FileDetailActivity" />\r
<activity android:name=".ui.activity.PinCodeActivity" />\r
- <activity android:name=".extensions.ExtensionsAvailableActivity"></activity>
+ <activity android:name=".extensions.ExtensionsAvailableActivity"></activity>\r
<activity android:name=".extensions.ExtensionsListActivity"></activity>\r
<activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>\r
- <activity android:name=".ui.activity.ConflictsResolveActivity"/>
+ <activity android:name=".ui.activity.ConflictsResolveActivity"/>\r
<activity android:name=".ui.activity.GenericExplanationActivity"/>\r
<activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>\r
- <service android:name=".files.services.FileUploader" >\r
- </service>
<service android:name=".files.services.InstantUploadService" />
<receiver android:name=".files.InstantUploadBroadcastReceiver">\r
<intent-filter>\r
<action android:name="com.android.camera.NEW_PICTURE" />\r
<data android:mimeType="image/*" />\r
- </intent-filter>
- <intent-filter>
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
+ </intent-filter>\r
+ <intent-filter>\r
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>\r
</intent-filter>\r
</receiver>\r
<receiver android:name=".files.BootupBroadcastReceiver">\r
<action android:name="android.intent.action.BOOT_COMPLETED"/>\r
</intent-filter>\r
</receiver>\r
- <service android:name=".files.services.FileObserverService"/>
+ <service android:name=".files.services.FileObserverService"/>\r
</application>\r
\r
-</manifest>
+</manifest>\r
--- /dev/null
+## Submitting issues
+
+If you have questions about how to use ownCloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc].
+
+### Guidelines
+* [Report the issue](https://github.com/owncloud/android/issues/new) using our [template][template], it includes all the informations we need to track down the issue.
+* This repository is *only* for issues within the ownCloud Android app code. Issues in other compontents should be reported in their own repositores:
+ - [ownCloud code](https://github.com/owncloud/core/issues)
+ - [iOS client](https://github.com/owncloud/ios-issues/issues)
+ - [Desktop client](https://github.com/owncloud/mirall/issues)
+ - [ownCloud apps](https://github.com/owncloud/apps/issues) (e.g. Calendar, Contacts...)
+* Search the existing issues first, it's likely that your issue was already reported.
+
+If your issue appears to be a bug, and hasn't been reported, open a new issue.
+
+Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
+
+[template]: https://raw.github.com/owncloud/android/master/issue_template.md
+[mailinglist]: https://mail.kde.org/mailman/listinfo/owncloud
+[forum]: http://forum.owncloud.org/
+[irc]: http://webchat.freenode.net/?channels=owncloud&uio=d4
+
+## Contributing to Source Code
+
+Thanks for wanting to contribute source code to ownCloud. That's great!
+
+Before we're able to merge your code into the ownCloud app for Android, you need to sign our [Contributor Agreement][agreement].
+
+### Guidelines
+* Contribute your code in the branch 'develop'. It will give us a better chance to test your code before merging it with stable code.
+* For your first contribution, start a pull request on develop and send us the signed [Contributor Agreement][agreement].
+* Keep on using pull requests for your next contributions although you own write permissions.
+
+[agreement]: http://owncloud.org/about/contributor-agreement/
+
+## Translations
+Please submit translations via [Transifex][transifex].
+
+[transifex]: https://www.transifex.com/projects/p/owncloud/
--- /dev/null
+This is the android client for [owncloud][0].
+
+Make sure you read [SETUP.md][1] when you start working on this project.
+
+[0]: https://github.com/owncloud/core
+[1]: https://raw.github.com/owncloud/android/master/SETUP.md
\ No newline at end of file
+++ /dev/null
-If you want to start development of ownCloud android client you have two way to do so
-(a) building with console
-(b) building with eclipse
-
-1. Use setup_env.sh
-2a. ant clean;ant debug
-2b. Open Eclipse and import actionbarsherlock/library project to your workspace
-
-after those space you should be good to go.
-
-HAVE FUN!
-
-NOTE: You must have 'tools' and 'platforms-tools' in your path in order to run setup_env.sh
--- /dev/null
+
+ If you want to start development of ownCloud android client you have two way to do so:
+
+ 1. Building with console:
+
+ - Use setup_env.sh or setup_env.bat
+ - NOTE: You must have git, ant/bin, android/tools in your enviroment path
+
+ 2. Building with eclipse:
+
+ - Run ant clean debug
+ - Open Eclipse and import *actionbarsherlock/library* project to your workspace
+ - NOTE: You must have 'tools' and 'platforms-tools' in your path in order to run setup_env.sh
+
+ After those actions you should be good to go.
+
+ HAVE FUN!
+
+
+
+
+
+
--- /dev/null
+###################################################################
+ ownCloud Android client
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+ Copyright (C) 2012 Bartek Przybylski
+###################################################################
+
+
+###########
+# LICENSE #
+###########
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+The source distribution of this program should include a full copy
+of the GNU GPL version 2 license in the LICENSE.txt file located
+in its root directory. If not, see <http://www.gnu.org/licenses/>.
+
+
+########################
+# THIRD PARTY LICENSES #
+########################
+
+Both the source and binary distributions of this software contain
+some third party software. All the third party software included
+or linked is redistributed under the terms and conditions of their
+original licenses. These licenses are compatible the GPL license
+that govern this software, for the purposes they are being used.
+
+The third party software included and used by this project is:
+
+ * Apache JackRabbit, version 2.2.5.
+ Copyright (C) 2004-2010 The Apache Software Foundation.
+ Licensed under Apache License, Version 2.0.
+ Placed at libs/jackrabbit-webdav-2.2.5-jar-with-dependencies.jar
+ The jar file must be included in the ownCloud client APK.
+ Original license document included at libs/LICENSE.txt
+ See http://jackrabbit.apache.org/
+
+ * Transifex client.JavaMail API, version 1.4.3
+ Copyright (C) Transifex.
+ Licensed under GNU General Public License.
+ Placed at third_party/transifex-client.
+ Used as a helper tool, not included in the ownCloud client APK.
+ Original license document included at third_party/transifex-client/LICENSE.
+ See http://help.transifex.com/features/client/
+
+ * ActionBarSherlock, master branch.
+ Copyright (C) 2012 Jake Wharton.
+ Licensed under Apache License, Version 2.0.
+ The official repository is linked as a submodule in the
+ ownCloud/android repository.
+ A binary JAR file must be generated from this linked project
+ and included in the ownCloud client APK.
+ See http://http://actionbarsherlock.com/
+
\ No newline at end of file
--- /dev/null
+### Expected behaviour
+Tell us what should happen
+
+### Actual behaviour
+Tell us what happens instead
+
+### Steps to reproduce
+1.
+2.
+3.
+
+### Environment data
+Android version:
+
+Device model:
+
+Stock or customized system:
+
+ownCloud app version:
+
+ownCloud server version:
+
+### Logs
+#### Web server error log
+```
+Insert your webserver log here
+```
+
+#### ownCloud log (data/owncloud.log)
+```
+Insert your ownCloud log here
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.owncloud.android</groupId>
+ <artifactId>owncloud</artifactId>
+ <version>1.3.21-SNAPSHOT</version>
+ <packaging>apk</packaging>
+ <name>Owncloud Android</name>
+
+ <properties>
+ <java-version>1.6</java-version>
+ <google.android-version>4.1.1.4</google.android-version>
+ <google.android.support-version>r7</google.android.support-version>
+ <actionbarsherlock-version>4.2.0</actionbarsherlock-version>
+ </properties>
+
+ <url>https://github.com/owncloud/android</url>
+ <description>Owncloud for Android</description>
+ <scm>
+ <connection>scm:git:git@github.com:owncloud/android.git</connection>
+ <developerConnection>scm:git:git@github.com:owncloud/android.git</developerConnection>
+ <url>https://github.com/owncloud/android</url>
+ </scm>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>${google.android-version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>support-v4</artifactId>
+ <version>${google.android.support-version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.actionbarsherlock</groupId>
+ <artifactId>actionbarsherlock</artifactId>
+ <version>${actionbarsherlock-version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.actionbarsherlock</groupId>
+ <artifactId>actionbarsherlock</artifactId>
+ <version>${actionbarsherlock-version}</version>
+ <type>apklib</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-webdav</artifactId>
+ <version>2.5.2</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+
+ <sourceDirectory>src</sourceDirectory>
+
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.0</version>
+ <configuration>
+ <source>${java-version}</source>
+ <target>${java-version}</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <version>3.5.0</version>
+ <configuration>
+ <sdk>
+ <!-- platform or api level (api level 4 = platform 1.6)-->
+ <path>${env.ANDROID_HOME}</path>
+ <platform>17</platform>
+ </sdk>
+ </configuration>
+ <extensions>true</extensions>
+ </plugin>
+
+ </plugins>
+
+ </build>
+
+</project>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main_audio_view"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="Now playing:"
+ android:textSize="25sp"
+ android:textStyle="bold"
+ />
+ <TextView
+ android:id="@+id/now_playing_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dip"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_gravity="center"
+ android:text="Now playing.."
+ android:textSize="16sp"
+ android:textStyle="italic"
+ />
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- \r
+ ownCloud Android client application\r
+\r
+ Copyright (C) 2012 Bartek Przybylski\r
+ Copyright (C) 2012-2013 ownCloud Inc.\r
+ This program is free software: you can redistribute it and/or modify\r
+ it under the terms of the GNU General Public License as published by\r
+ the Free Software Foundation, either version 3 of the License, or\r
+ (at your option) any later version.\r
+\r
+ This program is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU General Public License for more details.\r
+\r
+ You should have received a copy of the GNU General Public License\r
+ along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+ -->\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ android:layout_width="fill_parent"\r
+ android:layout_height="fill_parent"\r
+ android:background="@color/owncloud_white"\r
+ android:orientation="vertical"\r
+ android:id="@+id/failed_files_list_view">\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_gravity="right"\r
+ android:orientation="horizontal" >\r
+\r
+ </LinearLayout>\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:orientation="vertical" >\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content" >\r
+\r
+ <TextView\r
+ android:id="@+id/failed_upload_headline_textview"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_weight="0.93"\r
+ android:hint="@string/failed_upload_headline_hint"\r
+ android:text="@string/failed_upload_headline_text" />\r
+\r
+ </LinearLayout>\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_gravity="bottom|right" >\r
+\r
+ <CheckBox\r
+ android:id="@+id/failed_upload_headline_cb"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:text="@string/failed_upload_all_cb"\r
+ android:textSize="8sp" />\r
+\r
+ <Button\r
+ android:id="@+id/failed_upload_retry_all_btn"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:minHeight="30dp"\r
+ android:minWidth="90dp"\r
+ android:text="@string/failed_upload_headline_retryall_btn"\r
+ android:textSize="8sp" />\r
+\r
+ <Button\r
+ android:id="@+id/failed_upload_delete_all_btn"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:minHeight="30dp"\r
+ android:minWidth="90dp"\r
+ android:text="@string/failed_upload_headline_delete_all_btn"\r
+ android:textSize="8sp" />\r
+\r
+ </LinearLayout>\r
+\r
+ </LinearLayout>\r
+ \r
+ <ScrollView\r
+ android:id="@+id/failedUploadScrollView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:overScrollMode="ifContentScrolls" >\r
+\r
+ <LinearLayout\r
+ android:id="@+id/failed_upload_scrollviewlayout"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:orientation="vertical">\r
+\r
+ \r
+ </LinearLayout>\r
+ </ScrollView>\r
+\r
+</LinearLayout>\r
--- /dev/null
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ android:id="@+id/failed_upload_message_view"\r
+ android:layout_width="fill_parent"\r
+ android:layout_height="fill_parent"\r
+ android:background="@color/owncloud_white"\r
+ android:orientation="vertical" >\r
+\r
+ <TextView android:id="@+id/faild_upload_message" \r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:minWidth="100dp"/>\r
+ \r
+ <Button\r
+ android:id="@+id/failed_uploadactivity_close_button"\r
+ android:layout_width="fill_parent"\r
+ android:layout_height="wrap_content"\r
+ android:text="Dismiss" />\r
+\r
+</LinearLayout>
\ No newline at end of file
GNU General Public License for more details.\r
\r
You should have received a copy of the GNU General Public License\r
- along with this program. If not, see <http://www.gnu.org/licenses/>.\r\r
+ along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+\r
-->\r
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
android:layout_width="fill_parent"\r
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@color/owncloud_white" >
-
- <ScrollView
- android:id="@+id/fdScrollView"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content" >
-
- <RelativeLayout
- android:id="@+id/fdFileHeaderContainer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
- android:layout_marginTop="4dp" >
-
- <ImageView
- android:id="@+id/fdIcon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/file" />
-
- <TextView
- android:id="@+id/fdFilename"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_toRightOf="@+id/fdIcon"
- android:text="file.name"
- android:textAppearance="?android:attr/textAppearanceLarge" />
- </RelativeLayout>
-
- <RelativeLayout
- android:id="@+id/fdDetailsContainer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdFileHeaderContainer" >
-
- <RelativeLayout
- android:id="@+id/fdLabelContainer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginLeft="16dp" >
-
- <TextView
- android:id="@+id/fdTypeLabel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="24dp"
- android:text="@string/filedetails_type"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdSizeLabel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdTypeLabel"
- android:layout_marginTop="12dp"
- android:text="@string/filedetails_size"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdCreatedLabel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdSizeLabel"
- android:layout_marginTop="12dp"
- android:text="@string/filedetails_created"
- android:visibility="gone"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdModifiedLabel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdCreatedLabel"
- android:layout_marginTop="12dp"
- android:text="@string/filedetails_modified"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </RelativeLayout>
-
- <RelativeLayout
- android:id="@+id/fdValueContainer"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_marginLeft="12dp"
- android:layout_toRightOf="@+id/fdLabelContainer" >
-
- <TextView
- android:id="@+id/fdType"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="24dp"
- android:text="JPG Image"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdSize"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdType"
- android:layout_marginTop="12dp"
- android:text="389 KB"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdCreated"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdSize"
- android:layout_marginTop="12dp"
- android:visibility="gone"
- android:text="2012/05/18 12:23 PM"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@+id/fdModified"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdCreated"
- android:layout_marginTop="12dp"
- android:text="2012/05/19 02:56 PM"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </RelativeLayout>
-
- </RelativeLayout>
-
- <RelativeLayout
- android:id="@+id/fdPreviewAndDL"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@+id/fdDetailsContainer"
- android:gravity="center_horizontal" >
-
- <CheckBox
- android:id="@+id/fdKeepInSync"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:text="@string/fd_keep_in_sync" />
-
- <ImageView
- android:id="@+id/fdPreview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/fdKeepInSync"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="16dp"
- android:src="@drawable/owncloud_logo" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/fdPreview"
- android:orientation="vertical" >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal" >
-
- <Button
- android:id="@+id/fdRemoveBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:text="@string/common_remove" />
-
- <Button
- android:id="@+id/fdOpenBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:text="@string/filedetails_open" />
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal">
-
- <Button
- android:id="@+id/fdDownloadBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:text="@string/filedetails_download" />
-
- <Button
- android:id="@+id/fdRenameBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:text="@string/common_rename" />
-
- </LinearLayout>
-<!--
- <Button
- android:id="@+id/fdShareBtn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
- android:text="@string/common_share" />
- -->
-
- </LinearLayout>
- </RelativeLayout>
-
- </RelativeLayout>
- </ScrollView>
-
-</RelativeLayout>
\ No newline at end of file
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fdScrollView"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <RelativeLayout
+ android:id="@+id/fdFileHeaderContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_marginTop="4dp" >
+
+ <ImageView
+ android:id="@+id/fdIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/file" />
+
+ <TextView
+ android:id="@+id/fdFilename"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@+id/fdIcon"
+ android:text="@string/placeholder_filename"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:id="@+id/fdDetailsContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/fdFileHeaderContainer" >
+
+ <RelativeLayout
+ android:id="@+id/fdLabelContainer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="16dp" >
+
+ <TextView
+ android:id="@+id/fdTypeLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:text="@string/filedetails_type"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdSizeLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdTypeLabel"
+ android:layout_marginTop="12dp"
+ android:text="@string/filedetails_size"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdCreatedLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdSizeLabel"
+ android:layout_marginTop="12dp"
+ android:text="@string/filedetails_created"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdModifiedLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdCreatedLabel"
+ android:layout_marginTop="12dp"
+ android:text="@string/filedetails_modified"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:id="@+id/fdValueContainer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="12dp"
+ android:layout_toRightOf="@+id/fdLabelContainer" >
+
+ <TextView
+ android:id="@+id/fdType"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:text="@string/placeholder_filetype"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdSize"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdType"
+ android:layout_marginTop="12dp"
+ android:text="@string/placeholder_filesize"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdCreated"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdSize"
+ android:layout_marginTop="12dp"
+ android:visibility="gone"
+ android:text="@string/placeholder_timestamp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/fdModified"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdCreated"
+ android:layout_marginTop="12dp"
+ android:text="@string/placeholder_timestamp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:id="@+id/fdProgressAndControl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/fdDetailsContainer"
+ android:gravity="center_horizontal"
+ android:layout_margin="16dp"
+ >
+
+ <CheckBox
+ android:id="@+id/fdKeepInSync"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="@string/fd_keep_in_sync" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/fdKeepInSync"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/fdProgressText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/downloader_download_in_progress_ticker"
+ />
+
+ <ProgressBar android:id="@+id/fdProgressBar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:progressDrawable="@android:drawable/progress_horizontal"
+ android:indeterminate="false"
+ android:indeterminateOnly="false"
+ />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginTop="12dp"
+ >
+
+ <Button
+ android:id="@+id/fdDownloadBtn"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/filedetails_download" />
+
+ <Button
+ android:id="@+id/fdOpenBtn"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/filedetails_open" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginTop="12dp"
+ >
+
+ <Button
+ android:id="@+id/fdRenameBtn"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_rename" />
+
+ <Button
+ android:id="@+id/fdRemoveBtn"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_remove" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+</ScrollView>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center_vertical"
+ android:padding="20dp"
+ >
+
+ <TextView
+ android:id="@+id/progressText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/downloader_not_downloaded_yet"
+ android:layout_marginBottom="15dp"
+ />
+
+ <ProgressBar android:id="@+id/progressBar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:progressDrawable="@android:drawable/progress_horizontal"
+ android:indeterminate="false"
+ android:indeterminateOnly="false"
+ android:layout_marginBottom="15dp"
+ />
+
+ <Button
+ android:id="@+id/cancelBtn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/common_cancel"
+ android:layout_marginBottom="15dp"
+ />
+
+ <ImageView
+ android:id="@+id/error_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="0dp"
+ android:layout_gravity="center_horizontal"
+ android:contentDescription="@string/downloader_download_failed_ticker"
+ android:src="@drawable/image_fail" />
+
+ <TextView
+ android:id="@+id/errorText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_margin="40dp"
+ android:text="@string/downloader_download_failed_ticker"
+ />
+
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/top"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/owncloud_white"
+ android:gravity="center"
+ tools:context=".ui.fragment.FilePreviewFragment" >
+
+ <FrameLayout
+ android:id="@+id/visual_area"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_alignParentTop="true"
+ android:layout_above="@+id/media_controller"
+ >
+
+ <ImageView
+ android:id="@+id/image_preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="16dp"
+ android:layout_gravity="center"
+ android:contentDescription="@string/preview_image_description"
+ android:src="@drawable/owncloud_logo" />
+
+ <VideoView
+ android:id="@+id/video_preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ />
+
+ </FrameLayout>
+
+ <com.owncloud.android.media.MediaControlView
+ android:id="@id/media_controller"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="4dip"
+ android:orientation="horizontal"
+ >
+
+ <ImageButton
+ android:id="@+id/rewindBtn"
+ style="@android:style/MediaButton.Rew"
+ android:contentDescription="@string/media_rewind_description"
+ />
+ <ImageButton
+ android:id="@+id/playBtn"
+ style="@android:style/MediaButton.Play"
+ android:contentDescription="@string/media_play_pause_description"
+ />
+ <ImageButton
+ android:id="@+id/forwardBtn"
+ style="@android:style/MediaButton.Ffwd"
+ android:contentDescription="@string/media_forward_description"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/currentTimeText"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:paddingTop="4dip"
+ android:paddingStart="4dip"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="4dip"
+ android:text="@string/placeholder_media_time"
+ />
+
+ <SeekBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="32dip"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentEnd="true" />
+
+ <TextView android:id="@+id/totalTimeText"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:paddingTop="4dip"
+ android:paddingEnd="4dip"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="4dip"
+ android:text="@string/placeholder_media_time"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/fragmentPager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
+ <!-- LinearLayout
+ android:id="@+id/fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+ <!- - Preview: layout=@layout/preview_image_fragment - ->
+ </LinearLayout -->
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+
+<!--
+ ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fdScrollView"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@color/owncloud_white"
+ android:gravity="center_horizontal"
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/top"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@color/owncloud_white"
+ tools:context=".ui.fragment.PreviewImageFragment" >
+
+ <ProgressBar
+ android:id="@+id/progressWheel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:layout_centerInParent="true"
+ />
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="0dp"
+ android:layout_centerInParent="true"
+ android:contentDescription="@string/preview_image_description"
+ android:src="@drawable/image_fail" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_below="@id/image"
+ android:layout_margin="40dp"
+ android:text="@string/placeholder_sentence"
+ />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <VideoView android:id="@+id/videoPlayer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+</FrameLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012 Bartek Przybylski
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/action_open_file_with" android:title="@string/filedetails_open" android:icon="@android:drawable/ic_menu_edit" android:orderInCategory="1" />
+ <item android:id="@+id/action_download_file" android:title="@string/filedetails_download" android:orderInCategory="1" />
+ <item android:id="@+id/action_cancel_download" android:title="@string/common_cancel_download" android:icon="@android:drawable/ic_menu_close_clear_cancel" android:orderInCategory="1" />
+ <item android:id="@+id/action_cancel_upload" android:title="@string/common_cancel_upload" android:icon="@android:drawable/ic_menu_close_clear_cancel" android:orderInCategory="1" />
+ <item android:id="@+id/action_rename_file" android:title="@string/common_rename" android:icon="@android:drawable/ic_menu_set_as" android:orderInCategory="1" />
+ <item android:id="@+id/action_remove_file" android:title="@string/common_remove" android:icon="@android:drawable/ic_menu_delete" android:orderInCategory="1" />
+ <item android:id="@+id/action_see_details" android:title="@string/actionbar_see_details" android:icon="@android:drawable/ic_menu_view" android:orderInCategory="1" />
+
+</menu>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ownCloud Android client application
-
- Copyright (C) 2012 Bartek Przybylski
- Copyright (C) 2012-2013 ownCloud Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
--->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/open_file_item"
- android:title="@string/filedetails_open"
- android:icon="@android:drawable/ic_menu_edit"
- />
-
- <item android:id="@+id/download_file_item"
- android:title="@string/filedetails_download"
- />
-
- <item android:id="@+id/cancel_download_item"
- android:title="@string/common_cancel_download"
- android:icon="@android:drawable/ic_menu_close_clear_cancel"
- />
-
- <item android:id="@+id/cancel_upload_item"
- android:title="@string/common_cancel_upload"
- android:icon="@android:drawable/ic_menu_close_clear_cancel"
- />
-
- <item android:id="@+id/rename_file_item"
- android:title="@string/common_rename"
- android:icon="@android:drawable/ic_menu_set_as"
- />
-
- <item android:id="@+id/remove_file_item"
- android:title="@string/common_remove"
- android:icon="@android:drawable/ic_menu_delete"
- />
-
-</menu>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012 Bartek Przybylski
+ Copyright (C) 2012-2013 ownCloud Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<menu
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/action_sync_account" android:title="@string/actionbar_sync" android:icon="@drawable/ic_action_refresh" android:orderInCategory="2" />
+ <item android:id="@+id/action_create_dir" android:title="@string/actionbar_mkdir" android:icon="@drawable/ic_action_create_dir" android:orderInCategory="2" />
+ <item android:id="@+id/action_upload" android:title="@string/actionbar_upload" android:icon="@drawable/ic_action_upload" android:orderInCategory="2" />
+ <item android:id="@+id/action_settings" android:title="@string/actionbar_settings" android:icon="@android:drawable/ic_menu_preferences" android:orderInCategory="2" />
+ <item android:id="@+id/action_about_app" android:title="@string/about_title" android:icon="@android:drawable/ic_menu_info_details" android:orderInCategory="2" />
+
+ <!-- <item android:id="@+id/search" android:title="@string/actionbar_search" android:icon="@drawable/ic_action_search"></item>-->
+</menu>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ownCloud Android client application
-
- Copyright (C) 2012 Bartek Przybylski
- Copyright (C) 2012-2013 ownCloud Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
--->
-<menu
- xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/startSync" android:title="@string/actionbar_sync" android:icon="@drawable/ic_action_refresh"></item>
- <item android:id="@+id/createDirectoryItem" android:title="@string/actionbar_mkdir" android:icon="@drawable/ic_action_create_dir"></item>
-
- <!-- <item android:id="@+id/search" android:title="@string/actionbar_search" android:icon="@drawable/ic_action_search"></item>-->
- <item android:id="@+id/action_upload" android:title="@string/actionbar_upload" android:icon="@drawable/ic_action_upload"></item>
- <item android:id="@+id/action_settings" android:title="@string/actionbar_settings" android:icon="@android:drawable/ic_menu_preferences"></item>
- <item android:id="@+id/about_app" android:title="@string/about_title" android:icon="@android:drawable/ic_menu_info_details"></item>
-</menu>
<string name="common_save_exit">Speichern & Schließen</string>
<string name="common_exit">%1$s verlassen</string>
<string name="common_error">Fehler</string>
+ <string name="common_loading">Wird geladen …</string>
+ <string name="common_error_unknown">Unbekannter Fehler</string>
<string name="about_title">Über</string>
<string name="delete_account">Account löschen</string>
<string name="create_account">Account erstellen</string>
<string name="downloader_download_succeeded_content">%1$s wurde erfolgreich heruntergeladen</string>
<string name="downloader_download_failed_ticker">Herunterladen fehlgeschlagen</string>
<string name="downloader_download_failed_content">Herunterladen von %1$s konnte nicht abgeschlossen werden</string>
+ <string name="downloader_not_downloaded_yet">Noch nicht heruntergeladen</string>
<string name="common_choose_account">Konto auswählen</string>
<string name="sync_string_contacts">Kontakte</string>
<string name="sync_fail_ticker">Synchronisation fehlgeschlagen</string>
<string name="pincode_wrong">Falsche App-PIN</string>
<string name="pincode_removed">Die App-PIN wurde entfernt</string>
<string name="pincode_stored">Die App-PIN wurde gespeichert</string>
+ <string name="media_notif_ticker">"%1$s Musik Player"</string>
+ <string name="media_state_playing">"%1$s (wird abgespielt)"</string>
+ <string name="media_state_loading">"%1$s (wird geladen)"</string>
+ <string name="media_event_done">"%1$s Wiedergabe beendet"</string>
+ <string name="media_err_nothing_to_play">Keine Media-Datei gefunden</string>
+ <string name="media_err_no_account">Ungültiger Account</string>
+ <string name="media_err_not_in_owncloud">Datei ist nicht in einem gültigen Account</string>
+ <string name="media_err_unsupported">Nicht unterstützter Medien-Codec</string>
+ <string name="media_err_io">Media-Datei konnte nicht gelesen werden</string>
+ <string name="media_err_malformed">Die Media-Datei ist noch nicht kodiert</string>
+ <string name="media_err_timeout">Zeitüberschreitung ist beim Abspielen aufgetreten</string>
+ <string name="media_err_invalid_progressive_playback">Media-Datei konnte nicht gestreamt werden</string>
+ <string name="media_err_unknown">Media-Datei kann nicht vom Standard Player wiedergegeben werden</string>
+ <string name="media_err_security_ex">Sicherheits-Fehler ist beim Abspielen aufgetreten %1$s</string>
+ <string name="media_err_io_ex">Eingabe-Fehler ist beim Abspielen aufgetreten %1$s</string>
+ <string name="media_err_unexpected">Unerwarteter Fehler bei der Wiedergabe %1$s</string>
+ <string name="media_rewind_description">Zurückspulen</string>
+ <string name="media_play_pause_description">Wiedergabe oder Pause</string>
+ <string name="media_forward_description">Vorspulen</string>
+
<string-array name="prefs_trackmydevice_intervall_keys">
<item>15 Minuten</item>
<item>30 Minuten</item>
<string name="conflict_keep_both">Beide behalten</string>
<string name="conflict_overwrite">Überschreiben</string>
<string name="conflict_dont_upload">Nicht hochladen</string>
+ <string name="preview_image_description">Bildvorschau</string>
+ <string name="preview_image_error_unknown_format">Das Bild kann nicht angezeigt werden</string>
+ <string name="preview_image_error_out_of_memory">"Nicht genug Speicherplatz um das Bild anzuzeigen</string>
+
+ <string name="actionbar_failed_instant_upload">Fehlgeschlagene Sofortuploads</string>
+ <string name="failed_upload_headline_text">Fehlgeschlagene Sofortuploads</string>
+ <string name="failed_upload_headline_hint">Auflistung aller fehlgeschlagenen Softuploads</string>
+ <string name="failed_upload_all_cb">Alle auswählen</string>
+ <string name="failed_upload_headline_retryall_btn">Ausgewählte wiederholen</string>
+ <string name="failed_upload_headline_delete_all_btn">Ausgewählte löschen </string>
+ <string name="failed_upload_retry_text">Versuche ausgewählte erneut hochzuladen</string>
+ <string name="failed_upload_load_more_images">Load more Picrures</string>
+ <string name="failed_upload_retry_do_nothing_text">do nothing you are not online for instant upload</string>
</resources>
<string name="conflict_keep_both">Beide behalten</string>
<string name="conflict_overwrite">Überschreiben</string>
<string name="conflict_dont_upload">Nicht hochladen</string>
+
+ <string name="actionbar_failed_instant_upload">Fehlgeschlagene Sofortuploads</string>
+ <string name="failed_upload_headline_text">Fehlgeschlagene Sofortuploads</string>
+ <string name="failed_upload_headline_hint">Auflistung aller fehlgeschlagenen Softuploads</string>
+ <string name="failed_upload_all_cb">Alle auswählen</string>
+ <string name="failed_upload_headline_retryall_btn">Ausgewählte wiederholen</string>
+ <string name="failed_upload_headline_delete_all_btn">Ausgewählte löschen </string>
+ <string name="failed_upload_retry_text">Versuche ausgewählte erneut hochzuladen</string>
+ <string name="failed_upload_load_more_images">Weitere Bilder laden</string>
+ <string name="failed_upload_retry_do_nothing_text">Upload nicht gestarted, Sie sind nicht online für ein Softupload</string>
+ <string name="failed_upload_failure_text">Fehlermeldung: </string>
+ <string name="failed_upload_quota_exceeded_text">Bitte überprüfen sie ihre Serverkonfiguration, möglicherweise ist ihre Upload Limit überschritten</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
+
<string name="app_name">ownCloud</string>
<string name="main_password">Password:</string>
<string name="main_login">Username:</string>
<string name="main_settings">Settings</string>
<string name="main_tit_accsetup">Setup Account</string>
<string name="main_wrn_accsetup">There is no account set up on your device. In order to use this App, you need to create one.</string>
-
<string name="about_message">%1$s Android App\n\nversion: %2$s</string>
-
<string name="actionbar_sync">Refresh</string>
<string name="actionbar_upload">Upload</string>
<string name="actionbar_upload_from_apps">Content from other apps</string>
<string name="actionbar_mkdir">Create directory</string>
<string name="actionbar_search">Search</string>
<string name="actionbar_settings">Settings</string>
+ <string name="actionbar_see_details">Details</string>
<string name="prefs_category_general">General</string>
<string name="prefs_add_session">Add new session</string>
<string name="prefs_pincode_summary">Protect your client</string>
<string name="prefs_instant_upload">Enable instant uploads</string>
<string name="prefs_instant_upload_summary">Instantly upload photos taken by camera</string>
-
<string name="auth_host_url">URL</string>
<string name="auth_username">Username</string>
<string name="auth_password">Password</string>
<string name="uploader_wrn_no_account_text">There are no %1$s accounts on your device. Please setup an account first.</string>
<string name="uploader_wrn_no_account_setup_btn_text">Setup</string>
<string name="uploader_wrn_no_account_quit_btn_text">Quit</string>
- <string name="uploader_wrn_no_content_title">No content to upload</string>
- <string name="uploader_wrn_no_content_text">No content was received. Nothing to upload.</string>
+ <string name="uploader_wrn_no_content_title">No content to upload</string>
+ <string name="uploader_wrn_no_content_text">No content was received. Nothing to upload.</string>
<string name="uploader_error_forbidden_content">%1$s is not allowed to access the shared content</string>
<string name="uploader_info_uploading">Uploading</string>
<string name="uploader_btn_create_dir_text">Create directory for upload</string>
- <string name="file_list_empty">There are no files in this folder.\nNew files can be added with the \"Upload\" menu option.</string>
+ <string name="file_list_empty">There are no files in this folder.\nNew files can be added with the \"Upload\" menu option.</string>
<string name="filedetails_select_file">Tap on a file to display additional information.</string>
<string name="filedetails_size">Size:</string>
<string name="filedetails_type">Type:</string>
<string name="filedetails_modified">Modified:</string>
<string name="filedetails_download">Download</string>
<string name="filedetails_sync_file">Refresh</string>
- <string name="filedetails_redownload">Redownload</string>
+ <string name="filedetails_redownload">Redownload</string>
<string name="filedetails_open">Open</string>
<string name="filedetails_renamed_in_upload_msg">File was renamed to %1$s during upload</string>
<string name="common_yes">Yes</string>
<string name="common_ok">OK</string>
<string name="common_cancel_download">Cancel download</string>
<string name="common_cancel_upload">Cancel upload</string>
- <string name="common_cancel">Cancel</string>
+ <string name="common_cancel">Cancel</string>
<string name="common_save_exit">Save & Exit</string>
<string name="common_exit">Leave %1$s</string>
<string name="common_error">Error</string>
+ <string name="common_loading">Loading …</string>
+ <string name="common_error_unknown">Unknown error</string>
<string name="about_title">About</string>
-
<string name="delete_account">Delete account</string>
<string name="create_account">Create account</string>
-
<string name="upload_chooser_title">Upload from …</string>
<string name="uploader_info_dirname">Directory name</string>
- <string name="uploader_upload_in_progress_ticker">Uploading …</string>
- <string name="uploader_upload_in_progress_content">%1$d%% Uploading %2$s</string>
- <string name="uploader_upload_succeeded_ticker">Upload succeeded</string>
+ <string name="uploader_upload_in_progress_ticker">Uploading …</string>
+ <string name="uploader_upload_in_progress_content">%1$d%% Uploading %2$s</string>
+ <string name="uploader_upload_succeeded_ticker">Upload succeeded</string>
<string name="uploader_upload_succeeded_content_single">%1$s was successfully uploaded</string>
<string name="uploader_upload_succeeded_content_multiple">%1$d files were successfully uploaded</string>
<string name="uploader_upload_failed_ticker">Upload failed</string>
<string name="downloader_download_succeeded_content">%1$s was successfully downloaded</string>
<string name="downloader_download_failed_ticker">Download failed</string>
<string name="downloader_download_failed_content">Download of %1$s could not be completed</string>
+ <string name="downloader_not_downloaded_yet">Not downloaded yet</string>
<string name="common_choose_account">Choose account</string>
<string name="sync_string_contacts">Contacts</string>
- <string name="sync_fail_ticker">Synchronization failed</string>
+ <string name="sync_fail_ticker">Synchronization failed</string>
<string name="sync_fail_content">Synchronization of %1$s could not be completed</string>
- <string name="sync_conflicts_in_favourites_ticker">Conflicts found</string>
- <string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
+ <string name="sync_conflicts_in_favourites_ticker">Conflicts found</string>
+ <string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
<string name="sync_fail_in_favourites_ticker">Kept-in-sync files failed</string>
- <string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
- <string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
- <string name="sync_foreign_files_forgotten_content">%1$d files out of the %2$s directory could not be copied into</string>
- <string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
-
+ <string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
+ <string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
+ <string name="sync_foreign_files_forgotten_content">%1$d files out of the %2$s directory could not be copied into</string>
+ <string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
<string name="foreign_files_move">"Move all"</string>
<string name="foreign_files_success">"All files were moved"</string>
<string name="foreign_files_fail">"Some files could not be moved"</string>
<string name="foreign_files_local_text">"Local: %1$s"</string>
- <string name="foreign_files_remote_text">"Remote: %1$s"</string>
-
- <string name="upload_query_move_foreign_files">There is not space enough to copy the selected files into the %1$s folder. Would like to move them into instead? </string>
-
- <string name="use_ssl">Use Secure Connection</string>
+ <string name="foreign_files_remote_text">"Remote: %1$s"</string>
+ <string name="upload_query_move_foreign_files">There is not space enough to copy the selected files into the %1$s folder. Would like to move them into instead? </string>
+ <string name="use_ssl">Use Secure Connection</string>
<string name="location_no_provider">%1$s cannot track your device. Please check your location settings</string>
-
<string name="pincode_enter_pin_code">Please, insert your App PIN</string>
<string name="pincode_enter_new_pin_code">Please, insert your new App PIN</string>
<string name="pincode_configure_your_pin">Enter your App PIN</string>
- <string name="pincode_configure_your_pin_explanation">The PIN will be requested every time the app is started</string>
+ <string name="pincode_configure_your_pin_explanation">The PIN will be requested every time the app is started</string>
<string name="pincode_reenter_your_pincode">Please, reenter your App PIN</string>
<string name="pincode_remove_your_pincode">Remove your App PIN</string>
- <string name="pincode_mismatch">The App PINs are not the same</string>
+ <string name="pincode_mismatch">The App PINs are not the same</string>
<string name="pincode_wrong">Incorrect App PIN</string>
<string name="pincode_removed">App PIN removed</string>
<string name="pincode_stored">App PIN stored</string>
+ <string name="media_notif_ticker">"%1$s music player"</string>
+ <string name="media_state_playing">"%1$s (playing)"</string>
+ <string name="media_state_loading">"%1$s (loading)"</string>
+ <string name="media_event_done">"%1$s playback finished"</string>
+ <string name="media_err_nothing_to_play">No media file found</string>
+ <string name="media_err_no_account">No account provided</string>
+ <string name="media_err_not_in_owncloud">File not in a valid account</string>
+ <string name="media_err_unsupported">Unsupported media codec</string>
+ <string name="media_err_io">Media file could not be read</string>
+ <string name="media_err_malformed">Media file not correctly encoded</string>
+ <string name="media_err_timeout">Too much time trying to play</string>
+ <string name="media_err_invalid_progressive_playback">Media file cannot be streamed</string>
+ <string name="media_err_unknown">Media file cannot be played with the stock media player</string>
+ <string name="media_err_security_ex">Security error trying to play %1$s</string>
+ <string name="media_err_io_ex">Input error trying to play %1$s</string>
+ <string name="media_err_unexpected">Unexpected error trying to play %1$s</string>
+ <string name="media_previous_description">Previous track button</string>
+ <string name="media_rewind_description">Rewind button</string>
+ <string name="media_play_pause_description">Play or pause button</string>
+ <string name="media_forward_description">Fast forward button</string>
+ <string name="media_next_description">Next track button</string>
<string-array name="prefs_trackmydevice_intervall_keys">
- <item>15 Minutes</item>
- <item>30 Minutes</item>
- <item>60 Minutes</item>
- </string-array>
-
+ <item>15 Minutes</item>
+ <item>30 Minutes</item>
+ <item>60 Minutes</item>
+ </string-array>
<string-array name="prefs_trackmydevice_intervall_values">
- <item>15</item>
- <item>30</item>
- <item>60</item>
- </string-array>
+ <item>15</item>
+ <item>30</item>
+ <item>60</item>
+ </string-array>
+
<string name="auth_trying_to_login">Trying to login…</string>
<string name="auth_no_net_conn_title">No network connection</string>
<string name="auth_no_net_conn_message">No network connection has been detected, check your Internet connection and try again.</string>
<string name="auth_incorrect_path_message">Application couldn\'t find a server instance at the given path. Please check your path and try again.</string>
<string name="auth_timeout_title">The server took too long to respond</string>
<string name="auth_incorrect_address_title">Malformed URL</string>
- <string name="auth_ssl_general_error_title">SSL initialization failed</string>
- <string name="auth_ssl_unverified_server_title">Unverified SSL server\'s identity</string>
- <string name="auth_bad_oc_version_title">Unrecognized server version</string>
- <string name="auth_wrong_connection_title">Couldn\'t establish connection</string>
- <string name="auth_secure_connection">Secure connection established</string>
+ <string name="auth_ssl_general_error_title">SSL initialization failed</string>
+ <string name="auth_ssl_unverified_server_title">Unverified SSL server\'s identity</string>
+ <string name="auth_bad_oc_version_title">Unrecognized server version</string>
+ <string name="auth_wrong_connection_title">Couldn\'t establish connection</string>
+ <string name="auth_secure_connection">Secure connection established</string>
<string name="auth_login_details">Login details</string>
<string name="auth_unauthorized">Invalid login / password</string>
<string name="auth_not_found">Wrong path given</string>
<string name="auth_internal">Internal server error, code %1$d</string>
-
<string name="crashlog_message">Application terminated unexpectedly. Would you like to submit a crash report?</string>
<string name="crashlog_send_report">Send report</string>
<string name="crashlog_dont_send_report">Don\'t send report</string>
-
<string name="extensions_avail_title">Extensions available!</string>
<string name="extensions_avail_message">Looks like your server instance is supporting advanced extensions. Would you like to see extensions available for android ?</string>
<string name="fd_keep_in_sync">Keep file up to date</string>
<string name="common_share">Share</string>
<string name="common_rename">Rename</string>
<string name="common_remove">Remove</string>
-
- <string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
- <string name="confirmation_remove_folder_alert">"Do you really want to remove %1$s and its contents ?"</string>
- <string name="confirmation_remove_local">Local only</string>
- <string name="confirmation_remove_folder_local">Local contents only</string>
- <string name="confirmation_remove_remote">Remove from server</string>
- <string name="confirmation_remove_remote_and_local">Remote and local</string>
-
+ <string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
+ <string name="confirmation_remove_folder_alert">"Do you really want to remove %1$s and its contents ?"</string>
+ <string name="confirmation_remove_local">Local only</string>
+ <string name="confirmation_remove_folder_local">Local contents only</string>
+ <string name="confirmation_remove_remote">Remove from server</string>
+ <string name="confirmation_remove_remote_and_local">Remote and local</string>
<string name="remove_success_msg">"Removal succeeded"</string>
<string name="remove_fail_msg">"Removal failed"</string>
-
<string name="rename_dialog_title">Enter a new name</string>
<string name="rename_local_fail_msg">"Local copy could not be renamed; try a different name"</string>
<string name="rename_server_fail_msg">"Rename could not be completed"</string>
-
- <string name="sync_file_fail_msg">Remote file could not be checked</string>
- <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string>
-
+ <string name="sync_file_fail_msg">Remote file could not be checked</string>
+ <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string>
<string name="create_dir_fail_msg">Directory could not be created</string>
-
<string name="wait_a_moment">Wait a moment</string>
-
<string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please select the file from a different app"</string>
<string name="filedisplay_no_file_selected">No file was selected</string>
-
<string name="ssl_validator_title">Warning</string>
<string name="ssl_validator_header">The identity of the site could not be verified</string>
<string name="ssl_validator_reason_cert_not_trusted">- The server certificate is not trusted</string>
<string name="ssl_validator_label_issuer">Issued by:</string>
<string name="ssl_validator_label_CN">Common name:</string>
<string name="ssl_validator_label_O">Organization:</string>
- <string name="ssl_validator_label_OU">Organizational unit:</string>
- <string name="ssl_validator_label_C">Country:</string>
- <string name="ssl_validator_label_ST">State:</string>
- <string name="ssl_validator_label_L">Location:</string>
+ <string name="ssl_validator_label_OU">Organizational unit:</string>
+ <string name="ssl_validator_label_C">Country:</string>
+ <string name="ssl_validator_label_ST">State:</string>
+ <string name="ssl_validator_label_L">Location:</string>
<string name="ssl_validator_label_validity">Validity:</string>
<string name="ssl_validator_label_validity_from">From:</string>
<string name="ssl_validator_label_validity_to">To:</string>
<string name="ssl_validator_label_signature">Signature:</string>
<string name="ssl_validator_label_signature_algorithm">Algorithm:</string>
- <string name="text_placeholder">This is a placeholder</string>
+ <string name="placeholder_sentence">This is a placeholder</string>
+ <string name="placeholder_filename">placeholder.txt</string>
+ <string name="placeholder_filetype">PNG Image</string>
+ <string name="placeholder_filesize">389 KB</string>
+ <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
+ <string name="placeholder_media_time">12:23:45</string>
<string name="instant_upload_on_wifi">Upload pictures via WiFi only</string>
- <string name="instant_upload_path">/InstantUpload</string>
-
+ <string name="instant_upload_path">/InstantUpload</string>
<string name="conflict_title">Update conflict</string>
<string name="conflict_message">Remote file %s is not synchronized with local file. Continuing will replace content of file on server.</string>
<string name="conflict_keep_both">Keep both</string>
<string name="conflict_overwrite">Overwrite</string>
<string name="conflict_dont_upload">Don\'t upload</string>
+ <string name="preview_image_description">Image preview</string>
+ <string name="preview_image_error_unknown_format">This image can not be shown</string>
+ <string name="preview_image_error_out_of_memory">"Not enough memory to show this image</string>
+
<!-- we need to improve the communication of errors to the user -->
<string name="error__upload__local_file_not_copied">%1$s could not be copied to %2$s local directory</string>
-
+ <string name="actionbar_failed_instant_upload">Failed InstantUpload"</string>
+ <string name="failed_upload_headline_text">Failed instant uploads</string>
+ <string name="failed_upload_headline_hint">Summary of all failed instant uploads</string>
+ <string name="failed_upload_all_cb">select all</string>
+ <string name="failed_upload_headline_retryall_btn">retry all selected</string>
+ <string name="failed_upload_headline_delete_all_btn">delete all selected from uploadqueue</string>
+ <string name="failed_upload_retry_text">retry to upload the image: </string>
+ <string name="failed_upload_load_more_images">Load more Picrures</string>
+ <string name="failed_upload_retry_do_nothing_text">do nothing you are not online for instant upload</string>
+ <string name="failed_upload_failure_text">Failure Message: </string>
+ <string name="failed_upload_quota_exceeded_text">Please check your server configuration,maybe your quota is exceeded.</string>
</resources>
--- /dev/null
+git submodule init
+git submodule update
+android.bat update project -p actionbarsherlock\library --target 1
+android.bat update project -p . --target 1
+cp third_party\android-support-library\android-support-v4.jar actionbarsherlock\library\libs\android-support-v4.jar
+cd tests
+android.bat update test-project -m .. -p .
\ No newline at end of file
public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent);
public void moveDirectory(OCFile dir, String newPath);
+
+ public Vector<OCFile> getDirectoryImages(OCFile mParentFolder);
}
}
}
+ @Override
+ public Vector<OCFile> getDirectoryImages(OCFile directory) {
+ Vector<OCFile> ret = new Vector<OCFile>();
+ if (directory != null) {
+ // TODO better implementation, filtering in the access to database (if possible) instead of here
+ Vector<OCFile> tmp = getDirectoryContent(directory);
+ OCFile current = null;
+ for (int i=0; i<tmp.size(); i++) {
+ current = tmp.get(i);
+ if (current.isImage()) {
+ ret.add(current);
+ }
+ }
+ }
+ return ret;
+ }
+
}
import java.io.File;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.webkit.MimeTypeMap;
public class OCFile implements Parcelable, Comparable<OCFile> {
return 0;
}
+ /** @return 'True' if the file contains audio */
+ public boolean isAudio() {
+ return (mMimeType != null && mMimeType.startsWith("audio/"));
+ }
+
+ /** @return 'True' if the file contains video */
+ public boolean isVideo() {
+ return (mMimeType != null && mMimeType.startsWith("video/"));
+ }
+
+ /** @return 'True' if the file contains an image */
+ public boolean isImage() {
+ return ((mMimeType != null && mMimeType.startsWith("image/")) ||
+ getMimeTypeFromName().startsWith("image/"));
+ }
+
+ public String getMimeTypeFromName() {
+ String extension = "";
+ int pos = mRemotePath.lastIndexOf('.');
+ if (pos >= 0) {
+ extension = mRemotePath.substring(pos + 1);
+ }
+ String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
+ return (result != null) ? result : "";
+ }
+
}
-/* ownCloud Android client application\r
- * Copyright (C) 2011-2012 Bartek Przybylski\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
-package com.owncloud.android.db;\r
-\r
-import android.content.ContentValues;\r
-import android.content.Context;\r
-import android.database.Cursor;\r
-import android.database.sqlite.SQLiteDatabase;\r
-import android.database.sqlite.SQLiteOpenHelper;\r
-\r
-/**\r
- * Custom database helper for ownCloud\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
-public class DbHandler {\r
- private SQLiteDatabase mDB;\r
- private OpenerHelper mHelper;\r
- private final String mDatabaseName = "ownCloud";\r
- private final int mDatabaseVersion = 1;\r
- \r
- private final String TABLE_INSTANT_UPLOAD = "instant_upload";\r
-\r
- public DbHandler(Context context) {\r
- mHelper = new OpenerHelper(context);\r
- mDB = mHelper.getWritableDatabase();\r
- }\r
-\r
- public void close() {\r
- mDB.close();\r
- }\r
-\r
- public boolean putFileForLater(String filepath, String account) {\r
- ContentValues cv = new ContentValues();\r
- cv.put("path", filepath);\r
- cv.put("account", account);\r
- return mDB.insert(TABLE_INSTANT_UPLOAD, null, cv) != -1;\r
- }\r
- \r
- public Cursor getAwaitingFiles() {\r
- return mDB.query(TABLE_INSTANT_UPLOAD, null, null, null, null, null, null);\r
- }\r
- \r
- public void clearFiles() {\r
- mDB.delete(TABLE_INSTANT_UPLOAD, null, null);\r
- }\r
- \r
- /**\r
- * \r
- * @param localPath\r
- * @param accountName\r
- * @return true when one or more pendin files was removed\r
- */\r
- public boolean removeIUPendingFile(String localPath, String accountName) {\r
- return mDB.delete(TABLE_INSTANT_UPLOAD,\r
- "path = ?",\r
- new String[]{ localPath }) != 0;\r
- \r
- }\r
- \r
- private class OpenerHelper extends SQLiteOpenHelper {\r
- public OpenerHelper(Context context) {\r
- super(context, mDatabaseName, null, mDatabaseVersion);\r
- }\r
-\r
- @Override\r
- public void onCreate(SQLiteDatabase db) {\r
- db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " ("\r
- + " _id INTEGER PRIMARY KEY, "\r
- + " path TEXT,"\r
- + " account TEXT);");\r
- }\r
-\r
- @Override\r
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\r
- }\r
- }\r
-}\r
+/* ownCloud Android client application
+ * Copyright (C) 2011-2012 Bartek Przybylski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.db;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Custom database helper for ownCloud
+ *
+ * @author Bartek Przybylski
+ *
+ */
+public class DbHandler {
+ private SQLiteDatabase mDB;
+ private OpenerHelper mHelper;
+ private final String mDatabaseName = "ownCloud";
+ private final int mDatabaseVersion = 3;
+
+ private final String TABLE_INSTANT_UPLOAD = "instant_upload";
+
+ public static final int UPLOAD_STATUS_UPLOAD_LATER = 0;
+ public static final int UPLOAD_STATUS_UPLOAD_FAILED = 1;
+
+ public DbHandler(Context context) {
+ mHelper = new OpenerHelper(context);
+ mDB = mHelper.getWritableDatabase();
+ }
+
+ public void close() {
+ mDB.close();
+ }
+
+ public boolean putFileForLater(String filepath, String account, String message) {
+ ContentValues cv = new ContentValues();
+ cv.put("path", filepath);
+ cv.put("account", account);
+ cv.put("attempt", UPLOAD_STATUS_UPLOAD_LATER);
+ cv.put("message", message);
+ long result = mDB.insert(TABLE_INSTANT_UPLOAD, null, cv);
+ Log.d(TABLE_INSTANT_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath);
+ return result != -1;
+ }
+
+ public int updateFileState(String filepath, Integer status, String message) {
+ ContentValues cv = new ContentValues();
+ cv.put("attempt", status);
+ cv.put("message", message);
+ int result = mDB.update(TABLE_INSTANT_UPLOAD, cv, "path=?", new String[] { filepath });
+ Log.d(TABLE_INSTANT_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath);
+ return result;
+ }
+
+ public Cursor getAwaitingFiles() {
+ return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
+ }
+
+ public Cursor getFailedFiles() {
+ return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
+ }
+
+ public void clearFiles() {
+ mDB.delete(TABLE_INSTANT_UPLOAD, null, null);
+ }
+
+ /**
+ *
+ * @param localPath
+ * @return true when one or more pending files was removed
+ */
+ public boolean removeIUPendingFile(String localPath) {
+ long result = mDB.delete(TABLE_INSTANT_UPLOAD, "path = ?", new String[] { localPath });
+ Log.d(TABLE_INSTANT_UPLOAD, "delete returns with: " + result + " for file: " + localPath);
+ return result != 0;
+
+ }
+
+ private class OpenerHelper extends SQLiteOpenHelper {
+ public OpenerHelper(Context context) {
+ super(context, mDatabaseName, null, mDatabaseVersion);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT,"
+ + " account TEXT,attempt INTEGER,message TEXT);");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2) {
+ db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;");
+ }
+ db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;");
+
+ }
+ }
+}
import java.io.File;
-import com.owncloud.android.AccountUtils;
-import com.owncloud.android.authenticator.AccountAuthenticator;
-import com.owncloud.android.db.DbHandler;
-import com.owncloud.android.files.services.FileUploader;
-
import android.accounts.Account;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.util.Log;
import android.webkit.MimeTypeMap;
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.authenticator.AccountAuthenticator;
+import com.owncloud.android.db.DbHandler;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.utils.FileStorageUtils;
+
public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
- public static String INSTANT_UPLOAD_DIR = "/InstantUpload/";
private static String TAG = "PhotoTakenBroadcastReceiver";
- private static final String[] CONTENT_PROJECTION = { Media.DATA,
- Media.DISPLAY_NAME,
- Media.MIME_TYPE,
- Media.SIZE };
+ private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE };
private static String NEW_PHOTO_ACTION = "com.android.camera.NEW_PICTURE";
-
+
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received: " + intent.getAction());
if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) {
DbHandler db = new DbHandler(context);
String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH);
- if (!db.removeIUPendingFile(localPath,
- intent.getStringExtra(FileUploader.ACCOUNT_NAME))) {
+ if (!db.removeIUPendingFile(localPath)) {
Log.w(TAG, "Tried to remove non existing instant upload file " + localPath);
}
db.close();
return;
}
- Cursor c = context.getContentResolver().query(intent.getData(),
- CONTENT_PROJECTION,
- null, null, null);
-
+ Cursor c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null);
+
if (!c.moveToFirst()) {
Log.e(TAG, "Couldn't resolve given uri: " + intent.getDataString());
return;
String mime_type = c.getString(c.getColumnIndex(Media.MIME_TYPE));
c.close();
- Log.e(TAG, file_path+"");
-
- if (!isOnline(context) ||
- (instantUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
- DbHandler db = new DbHandler(context);
- db.putFileForLater(file_path, account.name);
- db.close();
+ Log.e(TAG, file_path + "");
+
+ // same always temporally the picture to upload
+ DbHandler db = new DbHandler(context);
+ db.putFileForLater(file_path, account.name, null);
+ db.close();
+
+ if (!isOnline(context) || (instantUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
return;
}
-
+
// register for upload finishe message
- // there is a litte problem with android API, we can register for particular
- // intent in registerReceiver but we cannot unregister from precise intent
+ // there is a litte problem with android API, we can register for
+ // particular
+ // intent in registerReceiver but we cannot unregister from precise
+ // intent
// we can unregister from entire listenings but thats suck a bit.
// On the other hand this might be only for dynamicly registered
// broadcast receivers, needs investigation.
IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
context.getApplicationContext().registerReceiver(this, filter);
-
+
Intent i = new Intent(context, FileUploader.class);
i.putExtra(FileUploader.KEY_ACCOUNT, account);
i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, INSTANT_UPLOAD_DIR + file_name);
+ i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(file_name));
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
Log.d(TAG, "Instant upload disabled, abording uploading");
return;
}
-
- if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) &&
- isOnline(context) &&
- (!instantUploadViaWiFiOnly(context) ||
- (instantUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true)) ) {
+
+ if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
+ && isOnline(context)
+ && (!instantUploadViaWiFiOnly(context) || (instantUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) {
DbHandler db = new DbHandler(context);
Cursor c = db.getAwaitingFiles();
if (c.moveToFirst()) {
String mimeType = null;
try {
- mimeType = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(
- f.getName().substring(f.getName().lastIndexOf('.') + 1));
-
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ f.getName().substring(f.getName().lastIndexOf('.') + 1));
+
} catch (Throwable e) {
Log.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName());
}
if (mimeType == null)
mimeType = "application/octet-stream";
-
+
Intent i = new Intent(context, FileUploader.class);
i.putExtra(FileUploader.KEY_ACCOUNT, account);
i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, INSTANT_UPLOAD_DIR + f.getName());
+ i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(f.getName()));
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
context.startService(i);
-
+
} else {
Log.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore");
}
- } while(c.moveToNext());
+ } while (c.moveToNext());
}
c.close();
db.close();
}
-
+
}
- private boolean isOnline(Context context) {
+ public static boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
}
-
- private boolean isConnectedViaWiFi(Context context) {
+
+ public static boolean isConnectedViaWiFi(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- return cm != null && cm.getActiveNetworkInfo() != null &&
- cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI &&
- cm.getActiveNetworkInfo().getState() == State.CONNECTED;
+ return cm != null && cm.getActiveNetworkInfo() != null
+ && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
+ && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
}
-
- private boolean instantUploadEnabled(Context context) {
+
+ public static boolean instantUploadEnabled(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false);
}
-
- private boolean instantUploadViaWiFiOnly(Context context) {
+
+ public static boolean instantUploadViaWiFiOnly(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_upload_on_wifi", false);
}
}
\r
import java.io.File;\r
import java.util.AbstractList;\r
+import java.util.HashMap;\r
import java.util.Iterator;\r
+import java.util.Map;\r
import java.util.Vector;\r
import java.util.concurrent.ConcurrentHashMap;\r
import java.util.concurrent.ConcurrentMap;\r
import com.owncloud.android.operations.RemoteOperationResult;\r
import com.owncloud.android.ui.activity.FileDetailActivity;\r
import com.owncloud.android.ui.fragment.FileDetailFragment;\r
+import com.owncloud.android.ui.preview.PreviewImageActivity;\r
+import com.owncloud.android.ui.preview.PreviewImageFragment;\r
\r
import android.accounts.Account;\r
import android.app.Notification;\r
import android.util.Log;\r
import android.widget.RemoteViews;\r
\r
+import com.owncloud.android.AccountUtils;\r
import com.owncloud.android.R;\r
import eu.alefzero.webdav.WebdavClient;\r
\r
DownloadFileOperation newDownload = new DownloadFileOperation(account, file); \r
mPendingDownloads.putIfAbsent(downloadKey, newDownload);\r
newDownload.addDatatransferProgressListener(this);\r
+ newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder);\r
requestedDownloads.add(downloadKey);\r
sendBroadcastNewDownload(newDownload);\r
\r
return mBinder;\r
}\r
\r
+\r
+ /**\r
+ * Called when ALL the bound clients were onbound.\r
+ */\r
+ @Override\r
+ public boolean onUnbind(Intent intent) {\r
+ ((FileDownloaderBinder)mBinder).clearListeners();\r
+ return false; // not accepting rebinding (default behaviour)\r
+ }\r
+\r
\r
/**\r
* Binder to let client components to perform operations on the queue of downloads.\r
* \r
* It provides by itself the available operations.\r
*/\r
- public class FileDownloaderBinder extends Binder {\r
+ public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {\r
+ \r
+ /** \r
+ * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance \r
+ */\r
+ private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();\r
+ \r
\r
/**\r
* Cancels a pending or current download of a remote file.\r
}\r
\r
\r
+ public void clearListeners() {\r
+ mBoundListeners.clear();\r
+ }\r
+\r
+\r
/**\r
* Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.\r
* \r
}\r
}\r
}\r
+\r
+ \r
+ /**\r
+ * Adds a listener interested in the progress of the download for a concrete file.\r
+ * \r
+ * @param listener Object to notify about progress of transfer. \r
+ * @param account ownCloud account holding the file of interest.\r
+ * @param file {@link OCfile} of interest for listener. \r
+ */\r
+ public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {\r
+ if (account == null || file == null || listener == null) return;\r
+ String targetKey = buildRemoteName(account, file);\r
+ mBoundListeners.put(targetKey, listener);\r
+ }\r
+ \r
+ \r
+ \r
+ /**\r
+ * Removes a listener interested in the progress of the download for a concrete file.\r
+ * \r
+ * @param listener Object to notify about progress of transfer. \r
+ * @param account ownCloud account holding the file of interest.\r
+ * @param file {@link OCfile} of interest for listener. \r
+ */\r
+ public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {\r
+ if (account == null || file == null || listener == null) return;\r
+ String targetKey = buildRemoteName(account, file);\r
+ if (mBoundListeners.get(targetKey) == listener) {\r
+ mBoundListeners.remove(targetKey);\r
+ }\r
+ }\r
+\r
+\r
+ @Override\r
+ public void onTransferProgress(long progressRate) {\r
+ // old way, should not be in use any more\r
+ }\r
+\r
+\r
+ @Override\r
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,\r
+ String fileName) {\r
+ String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile());\r
+ OnDatatransferProgressListener boundListener = mBoundListeners.get(key);\r
+ if (boundListener != null) {\r
+ boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);\r
+ }\r
+ }\r
+ \r
}\r
\r
\r
mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);\r
\r
/// includes a pending intent in the notification showing the details view of the file\r
- Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ Intent showDetailsIntent = null;\r
+ if (PreviewImageFragment.canBePreviewed(download.getFile())) {\r
+ showDetailsIntent = new Intent(this, PreviewImageActivity.class);\r
+ } else {\r
+ showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ }\r
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());\r
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount());\r
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
int contentId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content;\r
Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis());\r
finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- // TODO put something smart in the contentIntent below\r
- finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
+ Intent showDetailsIntent = null;\r
+ if (downloadResult.isSuccess()) {\r
+ if (PreviewImageFragment.canBePreviewed(download.getFile())) {\r
+ showDetailsIntent = new Intent(this, PreviewImageActivity.class);\r
+ } else {\r
+ showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ }\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount());\r
+ showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
+ \r
+ } else {\r
+ // TODO put something smart in showDetailsIntent\r
+ showDetailsIntent = new Intent();\r
+ }\r
+ finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0);\r
finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), new File(download.getSavePath()).getName()), finalNotification.contentIntent);\r
mNotificationManager.notify(tickerId, finalNotification);\r
}\r
*/\r
private void sendBroadcastNewDownload(DownloadFileOperation download) {\r
Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE);\r
- /*added.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
- added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());*/\r
+ added.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
+ added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());\r
added.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
sendStickyBroadcast(added);\r
}\r
import java.io.File;
import java.util.AbstractList;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
-import com.owncloud.android.authenticator.AccountAuthenticator;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.InstantUploadBroadcastReceiver;
-import com.owncloud.android.operations.ChunkedUploadFileOperation;
-import com.owncloud.android.operations.RemoteOperationResult;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.ui.activity.FileDetailActivity;
-import com.owncloud.android.ui.fragment.FileDetailFragment;
-import com.owncloud.android.utils.OwnCloudVersion;
-
-import eu.alefzero.webdav.OnDatatransferProgressListener;
-import eu.alefzero.webdav.WebdavEntry;
-import eu.alefzero.webdav.WebdavUtils;
-
-import com.owncloud.android.network.OwnCloudClientUtils;
-
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Notification;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.RemoteViews;
+import android.widget.Toast;
import com.owncloud.android.R;
+import com.owncloud.android.authenticator.AccountAuthenticator;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.DbHandler;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.ChunkedUploadFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.ui.activity.FailedUploadActivity;
+import com.owncloud.android.ui.activity.FileDetailActivity;
+import com.owncloud.android.ui.activity.InstantUploadActivity;
+import com.owncloud.android.ui.fragment.FileDetailFragment;
+import com.owncloud.android.ui.preview.PreviewImageActivity;
+import com.owncloud.android.ui.preview.PreviewImageFragment;
+import com.owncloud.android.utils.OwnCloudVersion;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
import eu.alefzero.webdav.WebdavClient;
+import eu.alefzero.webdav.WebdavEntry;
+import eu.alefzero.webdav.WebdavUtils;
public class FileUploader extends Service implements OnDatatransferProgressListener {
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
- public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
-
+ public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
+
public static final String KEY_FILE = "FILE";
public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
public static final String KEY_MIME_TYPE = "MIME_TYPE";
public static final String KEY_ACCOUNT = "ACCOUNT";
-
+
public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
-
+
public static final int LOCAL_BEHAVIOUR_COPY = 0;
public static final int LOCAL_BEHAVIOUR_MOVE = 1;
public static final int LOCAL_BEHAVIOUR_FORGET = 2;
public static final int UPLOAD_MULTIPLE_FILES = 1;
private static final String TAG = FileUploader.class.getSimpleName();
-
+
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private IBinder mBinder;
private ConcurrentMap<String, UploadFileOperation> mPendingUploads = new ConcurrentHashMap<String, UploadFileOperation>();
private UploadFileOperation mCurrentUpload = null;
-
+
private NotificationManager mNotificationManager;
private Notification mNotification;
private int mLastPercent;
private RemoteViews mDefaultNotificationContentView;
-
-
+
/**
* Builds a key for mPendingUploads from the account and file to upload
*
- * @param account Account where the file to download is stored
- * @param file File to download
+ * @param account Account where the file to upload is stored
+ * @param file File to upload
*/
private String buildRemoteName(Account account, OCFile file) {
return account.name + file.getRemotePath();
return account.name + remotePath;
}
-
/**
* Checks if an ownCloud server version should support chunked uploads.
*
- * @param version OwnCloud version instance corresponding to an ownCloud server.
- * @return 'True' if the ownCloud server with version supports chunked uploads.
+ * @param version OwnCloud version instance corresponding to an ownCloud
+ * server.
+ * @return 'True' if the ownCloud server with version supports chunked
+ * uploads.
*/
private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
}
-
-
/**
* Service initialization
*/
@Override
public void onCreate() {
super.onCreate();
+ Log.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- HandlerThread thread = new HandlerThread("FileUploaderThread",
- Process.THREAD_PRIORITY_BACKGROUND);
+ HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper, this);
mBinder = new FileUploaderBinder();
}
-
/**
* Entry point to add one or several files to the queue of uploads.
*
- * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working
- * although the caller activity goes away.
+ * New uploads are added calling to startService(), resulting in a call to
+ * this method. This ensures the service will keep on working although the
+ * caller activity goes away.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE) || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
+ if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)
+ || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
Log.e(TAG, "Not enough information provided in intent");
return Service.START_NOT_STICKY;
}
return Service.START_NOT_STICKY;
}
Account account = intent.getParcelableExtra(KEY_ACCOUNT);
-
+
String[] localPaths = null, remotePaths = null, mimeTypes = null;
OCFile[] files = null;
if (uploadType == UPLOAD_SINGLE_FILE) {
-
+
if (intent.hasExtra(KEY_FILE)) {
- files = new OCFile[] {intent.getParcelableExtra(KEY_FILE) };
-
+ files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
+
} else {
localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) };
mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
}
-
+
} else { // mUploadType == UPLOAD_MULTIPLE_FILES
-
+
if (intent.hasExtra(KEY_FILE)) {
- files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO will this casting work fine?
-
+ files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO
+ // will
+ // this
+ // casting
+ // work
+ // fine?
+
} else {
localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
}
FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
-
+
boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY);
boolean fixed = false;
if (isInstant) {
- fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST be done BEFORE calling obtainNewOCFileToUpload
+ fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST
+ // be
+ // done
+ // BEFORE
+ // calling
+ // obtainNewOCFileToUpload
}
-
+
if (intent.hasExtra(KEY_FILE) && files == null) {
Log.e(TAG, "Incorrect array for OCFiles provided in upload intent");
return Service.START_NOT_STICKY;
-
+
} else if (!intent.hasExtra(KEY_FILE)) {
if (localPaths == null) {
Log.e(TAG, "Incorrect array for local paths provided in upload intent");
Log.e(TAG, "Different number of remote paths and local paths!");
return Service.START_NOT_STICKY;
}
-
+
files = new OCFile[localPaths.length];
- for (int i=0; i < localPaths.length; i++) {
- files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), storageManager);
+ for (int i = 0; i < localPaths.length; i++) {
+ files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes != null) ? mimeTypes[i]
+ : (String) null), storageManager);
+ if (files[i] == null) {
+ // TODO @andromaex add failure Notiification
+ return Service.START_NOT_STICKY;
+ }
}
}
-
- OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
+
+ OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account,
+ AccountAuthenticator.KEY_OC_VERSION));
boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
AbstractList<String> requestedUploads = new Vector<String>();
String uploadKey = null;
UploadFileOperation newUpload = null;
try {
- for (int i=0; i < files.length; i++) {
+ for (int i = 0; i < files.length; i++) {
uploadKey = buildRemoteName(account, files[i].getRemotePath());
if (chunked) {
- newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
+ newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite,
+ localAction);
} else {
newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
}
- if (fixed && i==0) {
+ if (fixed && i == 0) {
newUpload.setRemoteFolderToBeCreated();
}
mPendingUploads.putIfAbsent(uploadKey, newUpload);
newUpload.addDatatransferProgressListener(this);
+ newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
requestedUploads.add(uploadKey);
}
-
+
} catch (IllegalArgumentException e) {
Log.e(TAG, "Not enough information provided in intent: " + e.getMessage());
return START_NOT_STICKY;
-
+
} catch (IllegalStateException e) {
Log.e(TAG, "Bad information provided in intent: " + e.getMessage());
return START_NOT_STICKY;
-
+
} catch (Exception e) {
Log.e(TAG, "Unexpected exception while processing upload intent", e);
return START_NOT_STICKY;
-
+
}
-
+
if (requestedUploads.size() > 0) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = requestedUploads;
mServiceHandler.sendMessage(msg);
}
-
+ Log.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
return Service.START_NOT_STICKY;
}
-
/**
- * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the addition of new files.
+ * Provides a binder object that clients can use to perform operations on
+ * the queue of uploads, excepting the addition of new files.
*
- * Implemented to perform cancellation, pause and resume of existing uploads.
+ * Implemented to perform cancellation, pause and resume of existing
+ * uploads.
*/
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
+
+ /**
+ * Called when ALL the bound clients were onbound.
+ */
+ @Override
+ public boolean onUnbind(Intent intent) {
+ ((FileUploaderBinder)mBinder).clearListeners();
+ return false; // not accepting rebinding (default behaviour)
+ }
+
/**
- * Binder to let client components to perform operations on the queue of uploads.
+ * Binder to let client components to perform operations on the queue of
+ * uploads.
*
- * It provides by itself the available operations.
+ * It provides by itself the available operations.
*/
- public class FileUploaderBinder extends Binder {
+ public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
+
+ /**
+ * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
+ */
+ private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
/**
* Cancels a pending or current upload of a remote file.
*
- * @param account Owncloud account where the remote file will be stored.
- * @param file A file in the queue of pending uploads
+ * @param account Owncloud account where the remote file will be stored.
+ * @param file A file in the queue of pending uploads
*/
public void cancel(Account account, OCFile file) {
UploadFileOperation upload = null;
}
+
+ public void clearListeners() {
+ mBoundListeners.clear();
+ }
+
+
+
+
/**
- * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
+ * Returns True when the file described by 'file' is being uploaded to
+ * the ownCloud account 'account' or waiting for it
*
- * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
+ * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
*
- * @param account Owncloud account where the remote file will be stored.
- * @param file A file that could be in the queue of pending uploads
+ * @param account Owncloud account where the remote file will be stored.
+ * @param file A file that could be in the queue of pending uploads
*/
public boolean isUploading(Account account, OCFile file) {
- if (account == null || file == null) return false;
+ if (account == null || file == null)
+ return false;
String targetKey = buildRemoteName(account, file);
synchronized (mPendingUploads) {
if (file.isDirectory()) {
- // this can be slow if there are many downloads :(
+ // this can be slow if there are many uploads :(
Iterator<String> it = mPendingUploads.keySet().iterator();
boolean found = false;
while (it.hasNext() && !found) {
}
}
}
+
+
+ /**
+ * Adds a listener interested in the progress of the upload for a concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param account ownCloud account holding the file of interest.
+ * @param file {@link OCfile} of interest for listener.
+ */
+ public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ if (account == null || file == null || listener == null) return;
+ String targetKey = buildRemoteName(account, file);
+ mBoundListeners.put(targetKey, listener);
+ }
+
+
+
+ /**
+ * Removes a listener interested in the progress of the upload for a concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param account ownCloud account holding the file of interest.
+ * @param file {@link OCfile} of interest for listener.
+ */
+ public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ if (account == null || file == null || listener == null) return;
+ String targetKey = buildRemoteName(account, file);
+ if (mBoundListeners.get(targetKey) == listener) {
+ mBoundListeners.remove(targetKey);
+ }
+ }
+
+
+ @Override
+ public void onTransferProgress(long progressRate) {
+ // old way, should not be in use any more
+ }
+
+
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
+ String fileName) {
+ String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile());
+ OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
+ if (boundListener != null) {
+ boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
+ }
+ }
+
}
-
-
-
-
- /**
- * Upload worker. Performs the pending uploads in the order they were requested.
+
+ /**
+ * Upload worker. Performs the pending uploads in the order they were
+ * requested.
*
- * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
+ * Created with the Looper of a new thread, started in
+ * {@link FileUploader#onCreate()}.
*/
private static class ServiceHandler extends Handler {
- // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+ // don't make it a final class, and don't remove the static ; lint will
+ // warn about a possible memory leak
FileUploader mService;
+
public ServiceHandler(Looper looper, FileUploader service) {
super(looper);
if (service == null)
}
}
-
-
-
/**
* Core upload method: sends the file(s) to upload
*
- * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
+ * @param uploadKey Key to access the upload to perform, contained in
+ * mPendingUploads
*/
public void uploadFile(String uploadKey) {
- synchronized(mPendingUploads) {
+ synchronized (mPendingUploads) {
mCurrentUpload = mPendingUploads.get(uploadKey);
}
-
+
if (mCurrentUpload != null) {
-
+
notifyUploadStart(mCurrentUpload);
-
- /// prepare client object to send requests to the ownCloud server
+ // / prepare client object to send requests to the ownCloud server
if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) {
mLastAccount = mCurrentUpload.getAccount();
mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver());
mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext());
}
-
- /// create remote folder for instant uploads
+
+ // / create remote folder for instant uploads
if (mCurrentUpload.isRemoteFolderToBeCreated()) {
- mUploadClient.createDirectory(InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR); // ignoring result; fail could just mean that it already exists, but local database is not synchronized; the upload will be tried anyway
+ mUploadClient.createDirectory(InstantUploadService.INSTANT_UPLOAD_DIR);
+ // ignoring result fail could just mean that it already exists,
+ // but local database is not synchronized the upload will be
+ // tried anyway
}
-
- /// perform the upload
+ // / perform the upload
RemoteOperationResult uploadResult = null;
try {
uploadResult = mCurrentUpload.execute(mUploadClient);
if (uploadResult.isSuccess()) {
saveUploadedFile();
}
-
+
} finally {
- synchronized(mPendingUploads) {
+ synchronized (mPendingUploads) {
mPendingUploads.remove(uploadKey);
+ Log.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
}
}
-
- /// notify result
+
+ // notify result
notifyUploadResult(uploadResult, mCurrentUpload);
-
sendFinalBroadcast(mCurrentUpload, uploadResult);
-
+
}
-
+
}
/**
* Saves a OC File after a successful upload.
*
- * A PROPFIND is necessary to keep the props in the local database synchronized with the server,
- * specially the modification time and Etag (where available)
+ * A PROPFIND is necessary to keep the props in the local database
+ * synchronized with the server, specially the modification time and Etag
+ * (where available)
*
* TODO refactor this ugly thing
*/
OCFile file = mCurrentUpload.getFile();
long syncDate = System.currentTimeMillis();
file.setLastSyncDateForData(syncDate);
-
- /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
+
+ // / new PROPFIND to keep data consistent with server in theory, should
+ // return the same we already have
PropFindMethod propfind = null;
RemoteOperationResult result = null;
try {
- propfind = new PropFindMethod(mUploadClient.getBaseUri() + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()));
- int status = mUploadClient.executeMethod(propfind);
- boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS);
- if (isMultiStatus) {
- MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
- WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
- mUploadClient.getBaseUri().getPath());
- updateOCFile(file, we);
- file.setLastSyncDateForProperties(syncDate);
-
- } else {
- mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream());
- }
-
- result = new RemoteOperationResult(isMultiStatus, status);
- Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage());
-
+ propfind = new PropFindMethod(mUploadClient.getBaseUri()
+ + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()));
+ int status = mUploadClient.executeMethod(propfind);
+ boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS);
+ if (isMultiStatus) {
+ MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
+ WebdavEntry we = new WebdavEntry(resp.getResponses()[0], mUploadClient.getBaseUri().getPath());
+ updateOCFile(file, we);
+ file.setLastSyncDateForProperties(syncDate);
+
+ } else {
+ mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream());
+ }
+
+ result = new RemoteOperationResult(isMultiStatus, status);
+ Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": "
+ + result.getLogMessage());
+
} catch (Exception e) {
result = new RemoteOperationResult(e);
- Log.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage(), e);
+ Log.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": "
+ + result.getLogMessage(), e);
} finally {
if (propfind != null)
propfind.releaseConnection();
}
- /// maybe this would be better as part of UploadFileOperation... or maybe all this method
+ // / maybe this would be better as part of UploadFileOperation... or
+ // maybe all this method
if (mCurrentUpload.wasRenamed()) {
OCFile oldFile = mCurrentUpload.getOldFile();
if (oldFile.fileExists()) {
oldFile.setStoragePath(null);
mStorageManager.saveFile(oldFile);
-
- } // else: it was just an automatic renaming due to a name coincidence; nothing else is needed, the storagePath is right in the instance returned by mCurrentUpload.getFile()
+
+ } // else: it was just an automatic renaming due to a name
+ // coincidence; nothing else is needed, the storagePath is right
+ // in the instance returned by mCurrentUpload.getFile()
}
-
+
mStorageManager.saveFile(file);
}
-
private void updateOCFile(OCFile file, WebdavEntry we) {
file.setCreationTimestamp(we.createTimestamp());
file.setFileLength(we.contentLength());
file.setMimetype(we.contentType());
file.setModificationTimestamp(we.modifiedTimestamp());
file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp());
- // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
+ // file.setEtag(mCurrentUpload.getEtag()); // TODO Etag, where available
}
-
-
+
private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager) {
- OCFile instantUploadDir = storageManager.getFileByPath(InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR);
+ OCFile instantUploadDir = storageManager.getFileByPath(InstantUploadService.INSTANT_UPLOAD_DIR);
if (instantUploadDir == null) {
- // first instant upload in the account, or never account not synchronized after the remote InstantUpload folder was created
- OCFile newDir = new OCFile(InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR);
+ // first instant upload in the account, or never account not
+ // synchronized after the remote InstantUpload folder was created
+ OCFile newDir = new OCFile(InstantUploadService.INSTANT_UPLOAD_DIR);
newDir.setMimetype("DIR");
- newDir.setParentId(storageManager.getFileByPath(OCFile.PATH_SEPARATOR).getFileId());
- storageManager.saveFile(newDir);
- return true;
+ OCFile path = storageManager.getFileByPath(OCFile.PATH_SEPARATOR);
+
+ if (path != null) {
+ newDir.setParentId(path.getFileId());
+ storageManager.saveFile(newDir);
+ return true;
+ } else {
+ return false;
+ }
+
}
return false;
}
-
- private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, FileDataStorageManager storageManager) {
+ private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType,
+ FileDataStorageManager storageManager) {
OCFile newFile = new OCFile(remotePath);
newFile.setStoragePath(localPath);
newFile.setLastSyncDateForProperties(0);
newFile.setLastSyncDateForData(0);
-
+
// size
if (localPath != null && localPath.length() > 0) {
File localFile = new File(localPath);
newFile.setFileLength(localFile.length());
newFile.setLastSyncDateForData(localFile.lastModified());
- } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
-
+ } // don't worry about not assigning size, the problems with localPath
+ // are checked when the UploadFileOperation instance is created
+
// MIME type
if (mimeType == null || mimeType.length() <= 0) {
try {
- mimeType = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(
- remotePath.substring(remotePath.lastIndexOf('.') + 1));
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ remotePath.substring(remotePath.lastIndexOf('.') + 1));
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Trying to find out MIME type of a file without extension: " + remotePath);
}
mimeType = "application/octet-stream";
}
newFile.setMimetype(mimeType);
-
+
// parent dir
String parentPath = new File(remotePath).getParent();
- parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR ;
+ parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
OCFile parentDir = storageManager.getFileByPath(parentPath);
if (parentDir == null) {
- throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath);
+ Toast t = Toast
+ .makeText(
+ getApplicationContext(),
+ "The first time the InstantUpload is running you must be online, so the target folder can successfully created by the upload process",
+ 30);
+ t.show();
+ return null;
}
long parentDirId = parentDir.getFileId();
newFile.setParentId(parentDirId);
return newFile;
}
-
/**
* Creates a status notification to show the upload progress
*
- * @param upload Upload operation starting.
+ * @param upload Upload operation starting.
*/
+ @SuppressWarnings("deprecation")
private void notifyUploadStart(UploadFileOperation upload) {
- /// create status notification with a progress bar
+ // / create status notification with a progress bar
mLastPercent = 0;
- mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker), System.currentTimeMillis());
+ mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker),
+ System.currentTimeMillis());
mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
mDefaultNotificationContentView = mNotification.contentView;
- mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout);
+ mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(),
+ R.layout.progressbar_layout);
mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false);
- mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
+ mNotification.contentView.setTextViewText(R.id.status_text,
+ String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
/// includes a pending intent in the notification showing the details view of the file
- Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ Intent showDetailsIntent = null;
+ if (PreviewImageFragment.canBePreviewed(upload.getFile())) {
+ showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+ } else {
+ showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
+ }
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile());
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount());
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0);
-
+ mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
+ (int) System.currentTimeMillis(), showDetailsIntent, 0);
+
mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification);
}
-
/**
* Callback method to update the progress bar in the status notification
*/
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) {
- int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
+ int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
if (percent != mLastPercent) {
mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, false);
String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
}
mLastPercent = percent;
}
-
-
+
/**
- * Callback method to update the progress bar in the status notification (old version)
+ * Callback method to update the progress bar in the status notification
+ * (old version)
*/
@Override
public void onTransferProgress(long progressRate) {
// NOTHING TO DO HERE ANYMORE
}
-
/**
* Updates the status notification with the result of an upload operation.
*
- * @param uploadResult Result of the upload operation.
- * @param upload Finished upload operation
+ * @param uploadResult Result of the upload operation.
+ * @param upload Finished upload operation
*/
private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
+ Log.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
if (uploadResult.isCancelled()) {
- /// cancelled operation -> silent removal of progress notification
+ // / cancelled operation -> silent removal of progress notification
mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
-
+
} else if (uploadResult.isSuccess()) {
- /// success -> silent update of progress notification to success message
- mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove the ongoing flag
+ // / success -> silent update of progress notification to success
+ // message
+ mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove
+ // the
+ // ongoing
+ // flag
mNotification.flags |= Notification.FLAG_AUTO_CANCEL;
mNotification.contentView = mDefaultNotificationContentView;
/// includes a pending intent in the notification showing the details view of the file
- Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ Intent showDetailsIntent = null;
+ if (PreviewImageFragment.canBePreviewed(upload.getFile())) {
+ showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+ } else {
+ showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
+ }
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile());
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount());
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0);
-
- mNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_succeeded_ticker),
- String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()),
- mNotification.contentIntent);
-
- mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT AN ERROR; uploader_upload_in_progress_ticker is the target, not a new notification
-
- /* Notification about multiple uploads: pending of update
- mNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_succeeded_ticker),
- String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
- mNotification.contentIntent);
- */
-
+ mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
+ (int) System.currentTimeMillis(), showDetailsIntent, 0);
+
+ mNotification.setLatestEventInfo(getApplicationContext(),
+ getString(R.string.uploader_upload_succeeded_ticker),
+ String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()),
+ mNotification.contentIntent);
+
+ mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT
+ // AN
+ DbHandler db = new DbHandler(this.getBaseContext());
+ db.removeIUPendingFile(mCurrentUpload.getFile().getStoragePath());
+ db.close();
+
} else {
- /// fail -> explicit failure notification
+
+ // / fail -> explicit failure notification
mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
- Notification finalNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis());
+ Notification finalNotification = new Notification(R.drawable.icon,
+ getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis());
finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
- // TODO put something smart in the contentIntent below
- finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
-
- String content = null;
- if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL ||
- uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) {
- // TODO we need a class to provide error messages for the users from a RemoteOperationResult and a RemoteOperation
- content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), getString(R.string.app_name));
+
+ String content = null;
+ if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL
+ || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) {
+ // TODO we need a class to provide error messages for the users
+ // from a RemoteOperationResult and a RemoteOperation
+ content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(),
+ getString(R.string.app_name));
+ } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
+ content = getString(R.string.failed_upload_quota_exceeded_text);
} else {
- content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName());
+ content = String
+ .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName());
}
- finalNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_failed_ticker),
- content,
- finalNotification.contentIntent);
-
+
+ // we add only for instant-uploads the InstantUploadActivity and the
+ // db entry
+ Intent detailUploadIntent = null;
+ if (upload.isInstant()) {
+ detailUploadIntent = new Intent(this, InstantUploadActivity.class);
+ detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount());
+ } else {
+ detailUploadIntent = new Intent(this, FailedUploadActivity.class);
+ detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content);
+ }
+ finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
+ (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT);
+
+ if (upload.isInstant()) {
+ DbHandler db = null;
+ try {
+ db = new DbHandler(this.getBaseContext());
+ String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode();
+ Log.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode());
+ if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
+ message = getString(R.string.failed_upload_quota_exceeded_text);
+ }
+ if (db.updateFileState(upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED,
+ message) == 0) {
+ db.putFileForLater(upload.getOriginalStoragePath(), upload.getAccount().name, message);
+ }
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ }
+ finalNotification.setLatestEventInfo(getApplicationContext(),
+ getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent);
+
mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification);
-
- /* Notification about multiple uploads failure: pending of update
- finalNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_failed_ticker),
- String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
- finalNotification.contentIntent);
- } */
}
-
+
}
-
-
+
/**
- * Sends a broadcast in order to the interested activities can update their view
+ * Sends a broadcast in order to the interested activities can update their
+ * view
*
- * @param upload Finished upload operation
- * @param uploadResult Result of the upload operation
+ * @param upload Finished upload operation
+ * @param uploadResult Result of the upload operation
*/
private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
Intent end = new Intent(UPLOAD_FINISH_MESSAGE);
- end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote path, after possible automatic renaming
+ end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
+ // path, after
+ // possible
+ // automatic
+ // renaming
if (upload.wasRenamed()) {
end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
}
sendStickyBroadcast(end);
}
-
}
import java.util.LinkedList;
import java.util.List;
-import com.owncloud.android.network.OwnCloudClientUtils;
-
-import eu.alefzero.webdav.WebdavClient;
-
import android.accounts.Account;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import eu.alefzero.webdav.WebdavClient;
+
public class InstantUploadService extends Service {
public static String KEY_FILE_PATH = "KEY_FILEPATH";
public static String KEY_MIME_TYPE = "KEY_MIMETYPE";
public static String KEY_DISPLAY_NAME = "KEY_FILENAME";
public static String KEY_ACCOUNT = "KEY_ACCOUNT";
-
+
private static String TAG = "InstantUploadService";
- private static String INSTANT_UPLOAD_DIR = "/InstantUpload";
+ // TODO make it configurable over the settings dialog
+ public static final String INSTANT_UPLOAD_DIR = "/InstantUpload";
private UploaderRunnable mUploaderRunnable;
-
+
@Override
public IBinder onBind(Intent arg0) {
return null;
}
-
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null ||
- !intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_DISPLAY_NAME) ||
- !intent.hasExtra(KEY_FILE_PATH) || !intent.hasExtra(KEY_FILE_SIZE) ||
- !intent.hasExtra(KEY_MIME_TYPE)) {
+ if (intent == null || !intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_DISPLAY_NAME)
+ || !intent.hasExtra(KEY_FILE_PATH) || !intent.hasExtra(KEY_FILE_SIZE)
+ || !intent.hasExtra(KEY_MIME_TYPE)) {
Log.w(TAG, "Not all required information was provided, abording");
return Service.START_NOT_STICKY;
}
-
+
if (mUploaderRunnable == null) {
mUploaderRunnable = new UploaderRunnable();
}
-
+
String filename = intent.getStringExtra(KEY_DISPLAY_NAME);
String filepath = intent.getStringExtra(KEY_FILE_PATH);
String mimetype = intent.getStringExtra(KEY_MIME_TYPE);
Account account = intent.getParcelableExtra(KEY_ACCOUNT);
long filesize = intent.getLongExtra(KEY_FILE_SIZE, -1);
-
+
mUploaderRunnable.addElementToQueue(filename, filepath, mimetype, filesize, account);
-
+
// starting new thread for new download doesnt seems like a good idea
// maybe some thread pool or single background thread would be better
Log.d(TAG, "Starting instant upload thread");
new Thread(mUploaderRunnable).start();
-
+
return Service.START_STICKY;
}
-
+
private class UploaderRunnable implements Runnable {
-
+
Object mLock;
List<HashMap<String, Object>> mHashMapList;
-
+
public UploaderRunnable() {
mHashMapList = new LinkedList<HashMap<String, Object>>();
mLock = new Object();
}
-
- public void addElementToQueue(String filename,
- String filepath,
- String mimetype,
- long length,
- Account account) {
+
+ public void addElementToQueue(String filename, String filepath, String mimetype, long length, Account account) {
HashMap<String, Object> new_map = new HashMap<String, Object>();
new_map.put(KEY_ACCOUNT, account);
new_map.put(KEY_DISPLAY_NAME, filename);
new_map.put(KEY_FILE_PATH, filepath);
new_map.put(KEY_MIME_TYPE, mimetype);
new_map.put(KEY_FILE_SIZE, length);
-
+
synchronized (mLock) {
mHashMapList.add(new_map);
}
}
-
+
private HashMap<String, Object> getFirstObject() {
synchronized (mLock) {
if (mHashMapList.size() == 0)
return ret;
}
}
-
+
public void run() {
HashMap<String, Object> working_map;
-
+
while ((working_map = getFirstObject()) != null) {
Account account = (Account) working_map.get(KEY_ACCOUNT);
String filename = (String) working_map.get(KEY_DISPLAY_NAME);
String mimetype = (String) working_map.get(KEY_MIME_TYPE);
WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(account, getApplicationContext());
-
- wdc.createDirectory(INSTANT_UPLOAD_DIR); // fail could just mean that it already exists; put will be tried anyway
+
+ wdc.createDirectory(INSTANT_UPLOAD_DIR); // fail could just mean that it already exists put will be tried anyway
try {
- wdc.putFile(filepath, INSTANT_UPLOAD_DIR + "/" + filename, mimetype);
+ wdc.putFile(filepath, FileStorageUtils.getInstantUploadFilePath(filename), mimetype);
} catch (Exception e) {
// nothing to do; this service is deprecated, indeed
}
}
}
}
-
+
}
--- /dev/null
+/* ownCloud Android client application
+ *
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.media;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.MediaController.MediaPlayerControl;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+import com.owncloud.android.R;
+
+/**
+ * View containing controls for a {@link MediaPlayer}.
+ *
+ * Holds buttons "play / pause", "rewind", "fast forward"
+ * and a progress slider.
+ *
+ * It synchronizes itself with the state of the
+ * {@link MediaPlayer}.
+ *
+ * @author David A. Velasco
+ */
+
+public class MediaControlView extends FrameLayout /* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener, OnSeekBarChangeListener {
+
+ private MediaPlayerControl mPlayer;
+ private Context mContext;
+ private View mRoot;
+ private ProgressBar mProgress;
+ private TextView mEndTime, mCurrentTime;
+ private boolean mDragging;
+ private static final int SHOW_PROGRESS = 1;
+ StringBuilder mFormatBuilder;
+ Formatter mFormatter;
+ private ImageButton mPauseButton;
+ private ImageButton mFfwdButton;
+ private ImageButton mRewButton;
+
+ public MediaControlView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+
+ FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ );
+ LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mRoot = inflate.inflate(R.layout.media_control, null);
+ initControllerView(mRoot);
+ addView(mRoot, frameParams);
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ requestFocus();
+ }
+
+ @Override
+ public void onFinishInflate() {
+ /*
+ if (mRoot != null)
+ initControllerView(mRoot);
+ */
+ }
+
+ /* TODO REMOVE
+ public MediaControlView(Context context, boolean useFastForward) {
+ super(context);
+ mContext = context;
+ mUseFastForward = useFastForward;
+ initFloatingWindowLayout();
+ //initFloatingWindow();
+ }
+ */
+
+ /* TODO REMOVE
+ public MediaControlView(Context context) {
+ this(context, true);
+ }
+ */
+
+ /* T
+ private void initFloatingWindow() {
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ mWindow = PolicyManager.makeNewWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mDecor = mWindow.getDecorView();
+ mDecor.setOnTouchListener(mTouchListener);
+ mWindow.setContentView(this);
+ mWindow.setBackgroundDrawableResource(android.R.color.transparent);
+
+ // While the media controller is up, the volume control keys should
+ // affect the media stream type
+ mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ requestFocus();
+ }
+ */
+
+ /*
+ // Allocate and initialize the static parts of mDecorLayoutParams. Must
+ // also call updateFloatingWindowLayout() to fill in the dynamic parts
+ // (y and width) before mDecorLayoutParams can be used.
+ private void initFloatingWindowLayout() {
+ mDecorLayoutParams = new WindowManager.LayoutParams();
+ WindowManager.LayoutParams p = mDecorLayoutParams;
+ p.gravity = Gravity.TOP;
+ p.height = LayoutParams.WRAP_CONTENT;
+ p.x = 0;
+ p.format = PixelFormat.TRANSLUCENT;
+ p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+ p.token = null;
+ p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
+ }
+ */
+
+ // Update the dynamic parts of mDecorLayoutParams
+ // Must be called with mAnchor != NULL.
+ /*
+ private void updateFloatingWindowLayout() {
+ int [] anchorPos = new int[2];
+ mAnchor.getLocationOnScreen(anchorPos);
+
+ WindowManager.LayoutParams p = mDecorLayoutParams;
+ p.width = mAnchor.getWidth();
+ p.y = anchorPos[1] + mAnchor.getHeight();
+ }
+ */
+
+ /*
+ // This is called whenever mAnchor's layout bound changes
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight,
+ int oldBottom) {
+ //updateFloatingWindowLayout();
+ if (mShowing) {
+ mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);
+ }
+ }
+ */
+
+ /*
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mShowing) {
+ hide();
+ }
+ }
+ return false;
+ }
+ */
+
+
+ public void setMediaPlayer(MediaPlayerControl player) {
+ mPlayer = player;
+ mHandler.sendEmptyMessage(SHOW_PROGRESS);
+ updatePausePlay();
+ }
+
+
+ private void initControllerView(View v) {
+ mPauseButton = (ImageButton) v.findViewById(R.id.playBtn);
+ if (mPauseButton != null) {
+ mPauseButton.requestFocus();
+ mPauseButton.setOnClickListener(this);
+ }
+
+ mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn);
+ if (mFfwdButton != null) {
+ mFfwdButton.setOnClickListener(this);
+ }
+
+ mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn);
+ if (mRewButton != null) {
+ mRewButton.setOnClickListener(this);
+ }
+
+ mProgress = (ProgressBar) v.findViewById(R.id.progressBar);
+ if (mProgress != null) {
+ if (mProgress instanceof SeekBar) {
+ SeekBar seeker = (SeekBar) mProgress;
+ seeker.setOnSeekBarChangeListener(this);
+ }
+ mProgress.setMax(1000);
+ }
+
+ mEndTime = (TextView) v.findViewById(R.id.totalTimeText);
+ mCurrentTime = (TextView) v.findViewById(R.id.currentTimeText);
+ mFormatBuilder = new StringBuilder();
+ mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+ }
+
+
+ /**
+ * Disable pause or seek buttons if the stream cannot be paused or seeked.
+ * This requires the control interface to be a MediaPlayerControlExt
+ */
+ private void disableUnsupportedButtons() {
+ try {
+ if (mPauseButton != null && !mPlayer.canPause()) {
+ mPauseButton.setEnabled(false);
+ }
+ if (mRewButton != null && !mPlayer.canSeekBackward()) {
+ mRewButton.setEnabled(false);
+ }
+ if (mFfwdButton != null && !mPlayer.canSeekForward()) {
+ mFfwdButton.setEnabled(false);
+ }
+ } catch (IncompatibleClassChangeError ex) {
+ // We were given an old version of the interface, that doesn't have
+ // the canPause/canSeekXYZ methods. This is OK, it just means we
+ // assume the media can be paused and seeked, and so we don't disable
+ // the buttons.
+ }
+ }
+
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ int pos;
+ switch (msg.what) {
+ case SHOW_PROGRESS:
+ pos = setProgress();
+ if (!mDragging) {
+ msg = obtainMessage(SHOW_PROGRESS);
+ sendMessageDelayed(msg, 1000 - (pos % 1000));
+ }
+ break;
+ }
+ }
+ };
+
+ private String stringForTime(int timeMs) {
+ int totalSeconds = timeMs / 1000;
+
+ int seconds = totalSeconds % 60;
+ int minutes = (totalSeconds / 60) % 60;
+ int hours = totalSeconds / 3600;
+
+ mFormatBuilder.setLength(0);
+ if (hours > 0) {
+ return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+ } else {
+ return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+ }
+ }
+
+ private int setProgress() {
+ if (mPlayer == null || mDragging) {
+ return 0;
+ }
+ int position = mPlayer.getCurrentPosition();
+ int duration = mPlayer.getDuration();
+ if (mProgress != null) {
+ if (duration > 0) {
+ // use long to avoid overflow
+ long pos = 1000L * position / duration;
+ mProgress.setProgress( (int) pos);
+ }
+ int percent = mPlayer.getBufferPercentage();
+ mProgress.setSecondaryProgress(percent * 10);
+ }
+
+ if (mEndTime != null)
+ mEndTime.setText(stringForTime(duration));
+ if (mCurrentTime != null)
+ mCurrentTime.setText(stringForTime(position));
+
+ return position;
+ }
+
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ final boolean uniqueDown = event.getRepeatCount() == 0
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ || keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (uniqueDown) {
+ doPauseResume();
+ //show(sDefaultTimeout);
+ if (mPauseButton != null) {
+ mPauseButton.requestFocus();
+ }
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (uniqueDown && !mPlayer.isPlaying()) {
+ mPlayer.start();
+ updatePausePlay();
+ //show(sDefaultTimeout);
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (uniqueDown && mPlayer.isPlaying()) {
+ mPlayer.pause();
+ updatePausePlay();
+ //show(sDefaultTimeout);
+ }
+ return true;
+ }
+
+ //show(sDefaultTimeout);
+ return super.dispatchKeyEvent(event);
+ }
+
+ public void updatePausePlay() {
+ if (mRoot == null || mPauseButton == null)
+ return;
+
+ if (mPlayer.isPlaying()) {
+ mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+ } else {
+ mPauseButton.setImageResource(android.R.drawable.ic_media_play);
+ }
+ }
+
+ private void doPauseResume() {
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause();
+ } else {
+ mPlayer.start();
+ }
+ updatePausePlay();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (mPauseButton != null) {
+ mPauseButton.setEnabled(enabled);
+ }
+ if (mFfwdButton != null) {
+ mFfwdButton.setEnabled(enabled);
+ }
+ if (mRewButton != null) {
+ mRewButton.setEnabled(enabled);
+ }
+ if (mProgress != null) {
+ mProgress.setEnabled(enabled);
+ }
+ disableUnsupportedButtons();
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int pos;
+ switch (v.getId()) {
+
+ case R.id.playBtn:
+ doPauseResume();
+ break;
+
+ case R.id.rewindBtn:
+ pos = mPlayer.getCurrentPosition();
+ pos -= 5000;
+ mPlayer.seekTo(pos);
+ setProgress();
+ break;
+
+ case R.id.forwardBtn:
+ pos = mPlayer.getCurrentPosition();
+ pos += 15000;
+ mPlayer.seekTo(pos);
+ setProgress();
+ break;
+
+ }
+ }
+
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ // We're not interested in programmatically generated changes to
+ // the progress bar's position.
+ return;
+ }
+
+ long duration = mPlayer.getDuration();
+ long newposition = (duration * progress) / 1000L;
+ mPlayer.seekTo( (int) newposition);
+ if (mCurrentTime != null)
+ mCurrentTime.setText(stringForTime( (int) newposition));
+ }
+
+ /**
+ * Called in devices with touchpad when the user starts to adjust the
+ * position of the seekbar's thumb.
+ *
+ * Will be followed by several onProgressChanged notifications.
+ */
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mDragging = true; // monitors the duration of dragging
+ mHandler.removeMessages(SHOW_PROGRESS); // grants no more updates with media player progress while dragging
+ }
+
+
+ /**
+ * Called in devices with touchpad when the user finishes the
+ * adjusting of the seekbar.
+ */
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mDragging = false;
+ setProgress();
+ updatePausePlay();
+ mHandler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(MediaControlView.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(MediaControlView.class.getName());
+ }
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright 2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.media;
+
+import android.accounts.Account;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.FileDetailActivity;
+import com.owncloud.android.ui.fragment.FileDetailFragment;
+
+/**
+ * Service that handles media playback, both audio and video.
+ *
+ * Waits for Intents which signal the service to perform specific operations: Play, Pause,
+ * Rewind, etc.
+ *
+ * @author David A. Velasco
+ */
+public class MediaService extends Service implements OnCompletionListener, OnPreparedListener,
+ OnErrorListener, AudioManager.OnAudioFocusChangeListener {
+
+ private static final String TAG = MediaService.class.getSimpleName();
+
+ private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media";
+
+ /// Intent actions that we are prepared to handle
+ public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE";
+ public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL";
+
+ /// Keys to add extras to the action
+ public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
+ public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT";
+ public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION";
+ public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD";
+
+
+ /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */
+ public static final int OC_MEDIA_ERROR = 0;
+
+ /** Time To keep the control panel visible when the user does not use it */
+ public static final int MEDIA_CONTROL_SHORT_LIFE = 4000;
+
+ /** Time To keep the control panel visible when the user does not use it */
+ public static final int MEDIA_CONTROL_PERMANENT = 0;
+
+ /** Volume to set when audio focus is lost and ducking is allowed */
+ private static final float DUCK_VOLUME = 0.1f;
+
+ /** Media player instance */
+ private MediaPlayer mPlayer = null;
+
+ /** Reference to the system AudioManager */
+ private AudioManager mAudioManager = null;
+
+
+ /** Values to indicate the state of the service */
+ enum State {
+ STOPPED,
+ PREPARING,
+ PLAYING,
+ PAUSED
+ };
+
+
+ /** Current state */
+ private State mState = State.STOPPED;
+
+ /** Possible focus values */
+ enum AudioFocus {
+ NO_FOCUS,
+ NO_FOCUS_CAN_DUCK,
+ FOCUS
+ }
+
+ /** Current focus state */
+ private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS;
+
+
+ /** 'True' when the current song is streaming from the network */
+ private boolean mIsStreaming = false;
+
+ /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */
+ private WifiLock mWifiLock;
+
+ private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK";
+
+ /** Notification to keep in the notification bar while a song is playing */
+ private NotificationManager mNotificationManager;
+ private Notification mNotification = null;
+
+ /** File being played */
+ private OCFile mFile;
+
+ /** Account holding the file being played */
+ private Account mAccount;
+
+ /** Flag signaling if the audio should be played immediately when the file is prepared */
+ protected boolean mPlayOnPrepared;
+
+ /** Position, in miliseconds, where the audio should be started */
+ private int mStartPosition;
+
+ /** Interface to access the service through binding */
+ private IBinder mBinder;
+
+ /** Control panel shown to the user to control the playback, to register through binding */
+ private MediaControlView mMediaController;
+
+
+
+ /**
+ * Helper method to get an error message suitable to show to users for errors occurred in media playback,
+ *
+ * @param context A context to access string resources.
+ * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
+ * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
+ * @return Message suitable to users.
+ */
+ public static String getMessageForMediaError(Context context, int what, int extra) {
+ int messageId;
+
+ if (what == OC_MEDIA_ERROR) {
+ messageId = extra;
+
+ } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {
+ /* Added in API level 17
+ Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature.
+ Constant Value: -1010 (0xfffffc0e)
+ */
+ messageId = R.string.media_err_unsupported;
+
+ } else if (extra == MediaPlayer.MEDIA_ERROR_IO) {
+ /* Added in API level 17
+ File or network related operation errors.
+ Constant Value: -1004 (0xfffffc14)
+ */
+ messageId = R.string.media_err_io;
+
+ } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) {
+ /* Added in API level 17
+ Bitstream is not conforming to the related coding standard or file spec.
+ Constant Value: -1007 (0xfffffc11)
+ */
+ messageId = R.string.media_err_malformed;
+
+ } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
+ /* Added in API level 17
+ Some operation takes too long to complete, usually more than 3-5 seconds.
+ Constant Value: -110 (0xffffff92)
+ */
+ messageId = R.string.media_err_timeout;
+
+ } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+ /* Added in API level 3
+ The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file.
+ Constant Value: 200 (0x000000c8)
+ */
+ messageId = R.string.media_err_invalid_progressive_playback;
+
+ } else {
+ /* MediaPlayer.MEDIA_ERROR_UNKNOWN
+ Added in API level 1
+ Unspecified media player error.
+ Constant Value: 1 (0x00000001)
+ */
+ /* MediaPlayer.MEDIA_ERROR_SERVER_DIED)
+ Added in API level 1
+ Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.
+ Constant Value: 100 (0x00000064)
+ */
+ messageId = R.string.media_err_unknown;
+ }
+ return context.getString(messageId);
+ }
+
+
+
+ /**
+ * Initialize a service instance
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "Creating ownCloud media service");
+
+ mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).
+ createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG);
+
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
+ mBinder = new MediaServiceBinder(this);
+ }
+
+
+ /**
+ * Entry point for Intents requesting actions, sent here via startService.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_PLAY_FILE)) {
+ processPlayFileRequest(intent);
+
+ } else if (action.equals(ACTION_STOP_ALL)) {
+ processStopRequest(true);
+ }
+
+ return START_NOT_STICKY; // don't want it to restart in case it's killed.
+ }
+
+
+ /**
+ * Processes a request to play a media file received as a parameter
+ *
+ * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want?
+ *
+ * @param intent Intent received in the request with the data to identify the file to play.
+ */
+ private void processPlayFileRequest(Intent intent) {
+ if (mState != State.PREPARING) {
+ mFile = intent.getExtras().getParcelable(EXTRA_FILE);
+ mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT);
+ mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false);
+ mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0);
+ tryToGetAudioFocus();
+ playMedia();
+ }
+ }
+
+
+ /**
+ * Processes a request to play a media file.
+ */
+ protected void processPlayRequest() {
+ // request audio focus
+ tryToGetAudioFocus();
+
+ // actually play the song
+ if (mState == State.STOPPED) {
+ // (re)start playback
+ playMedia();
+
+ } else if (mState == State.PAUSED) {
+ // continue playback
+ mState = State.PLAYING;
+ setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
+ configAndStartMediaPlayer();
+
+ }
+ }
+
+
+ /**
+ * Makes sure the media player exists and has been reset. This will create the media player
+ * if needed, or reset the existing media player if one already exists.
+ */
+ protected void createMediaPlayerIfNeeded() {
+ if (mPlayer == null) {
+ mPlayer = new MediaPlayer();
+
+ // make sure the CPU won't go to sleep while media is playing
+ mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+
+ // the media player will notify the service when it's ready preparing, and when it's done playing
+ mPlayer.setOnPreparedListener(this);
+ mPlayer.setOnCompletionListener(this);
+ mPlayer.setOnErrorListener(this);
+
+ } else {
+ mPlayer.reset();
+ }
+ }
+
+ /**
+ * Processes a request to pause the current playback
+ */
+ protected void processPauseRequest() {
+ if (mState == State.PLAYING) {
+ mState = State.PAUSED;
+ mPlayer.pause();
+ releaseResources(false); // retain media player in pause
+ // TODO polite audio focus, instead of keep it owned; or not?
+ }
+ }
+
+
+ /**
+ * Processes a request to stop the playback.
+ *
+ * @param force When 'true', the playback is stopped no matter the value of mState
+ */
+ protected void processStopRequest(boolean force) {
+ if (mState != State.PREPARING || force) {
+ mState = State.STOPPED;
+ mFile = null;
+ mAccount = null;
+ releaseResources(true);
+ giveUpAudioFocus();
+ stopSelf(); // service is no longer necessary
+ }
+ }
+
+
+ /**
+ * Releases resources used by the service for playback. This includes the "foreground service"
+ * status and notification, the wake locks and possibly the MediaPlayer.
+ *
+ * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
+ */
+ protected void releaseResources(boolean releaseMediaPlayer) {
+ // stop being a foreground service
+ stopForeground(true);
+
+ // stop and release the Media Player, if it's available
+ if (releaseMediaPlayer && mPlayer != null) {
+ mPlayer.reset();
+ mPlayer.release();
+ mPlayer = null;
+ }
+
+ // release the Wifi lock, if holding it
+ if (mWifiLock.isHeld()) {
+ mWifiLock.release();
+ }
+ }
+
+
+ /**
+ * Fully releases the audio focus.
+ */
+ private void giveUpAudioFocus() {
+ if (mAudioFocus == AudioFocus.FOCUS
+ && mAudioManager != null
+ && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) {
+
+ mAudioFocus = AudioFocus.NO_FOCUS;
+ }
+ }
+
+
+ /**
+ * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it.
+ */
+ protected void configAndStartMediaPlayer() {
+ if (mPlayer == null) {
+ throw new IllegalStateException("mPlayer is NULL");
+ }
+
+ if (mAudioFocus == AudioFocus.NO_FOCUS) {
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again
+ }
+
+ } else {
+ if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) {
+ mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);
+
+ } else {
+ mPlayer.setVolume(1.0f, 1.0f); // full volume
+ }
+
+ if (!mPlayer.isPlaying()) {
+ mPlayer.start();
+ }
+ }
+ }
+
+
+ /**
+ * Requests the audio focus to the Audio Manager
+ */
+ private void tryToGetAudioFocus() {
+ if (mAudioFocus != AudioFocus.FOCUS
+ && mAudioManager != null
+ && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN))
+ ) {
+ mAudioFocus = AudioFocus.FOCUS;
+ }
+ }
+
+
+ /**
+ * Starts playing the current media file.
+ */
+ protected void playMedia() {
+ mState = State.STOPPED;
+ releaseResources(false); // release everything except MediaPlayer
+
+ try {
+ if (mFile == null) {
+ Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+ return;
+
+ } else if (mAccount == null) {
+ Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+ return;
+ }
+
+ createMediaPlayerIfNeeded();
+ mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ String url = mFile.getStoragePath();
+ /* Streaming is not possible right now
+ if (url == null || url.length() <= 0) {
+ url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
+ }
+ mIsStreaming = url.startsWith("http:") || url.startsWith("https:");
+ */
+ mIsStreaming = false;
+
+ mPlayer.setDataSource(url);
+
+ mState = State.PREPARING;
+ setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName()));
+
+ // starts preparing the media player in background
+ mPlayer.prepareAsync();
+
+ // prevent the Wifi from going to sleep when streaming
+ if (mIsStreaming) {
+ mWifiLock.acquire();
+ } else if (mWifiLock.isHeld()) {
+ mWifiLock.release();
+ }
+
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e);
+ Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+
+ } catch (IOException e) {
+ Log.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e);
+ Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e);
+ Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e);
+ Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
+ processStopRequest(true);
+ }
+ }
+
+
+ /** Called when media player is done playing current song. */
+ public void onCompletion(MediaPlayer player) {
+ Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show();
+ if (mMediaController != null) {
+ // somebody is still bound to the service
+ player.seekTo(0);
+ processPauseRequest();
+ mMediaController.updatePausePlay();
+ } else {
+ // nobody is bound
+ processStopRequest(true);
+ }
+ return;
+ }
+
+
+ /**
+ * Called when media player is done preparing.
+ *
+ * Time to start.
+ */
+ public void onPrepared(MediaPlayer player) {
+ mState = State.PLAYING;
+ updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
+ if (mMediaController != null) {
+ mMediaController.setEnabled(true);
+ }
+ player.seekTo(mStartPosition);
+ configAndStartMediaPlayer();
+ if (!mPlayOnPrepared) {
+ processPauseRequest();
+ }
+
+ if (mMediaController != null) {
+ mMediaController.updatePausePlay();
+ }
+ }
+
+
+ /**
+ * Updates the status notification
+ */
+ @SuppressWarnings("deprecation")
+ private void updateNotification(String content) {
+ // TODO check if updating the Intent is really necessary
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
+ (int)System.currentTimeMillis(),
+ showDetailsIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mNotification.when = System.currentTimeMillis();
+ //mNotification.contentView.setTextViewText(R.id.status_text, content);
+ String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
+ mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
+ mNotificationManager.notify(R.string.media_notif_ticker, mNotification);
+ }
+
+
+ /**
+ * Configures the service as a foreground service.
+ *
+ * The system will avoid finishing the service as much as possible when resources as low.
+ *
+ * A notification must be created to keep the user aware of the existance of the service.
+ */
+ @SuppressWarnings("deprecation")
+ private void setUpAsForeground(String content) {
+ /// creates status notification
+ // TODO put a progress bar to follow the playback progress
+ mNotification = new Notification();
+ mNotification.icon = android.R.drawable.ic_media_play;
+ //mNotification.tickerText = text;
+ mNotification.when = System.currentTimeMillis();
+ mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
+ //mNotification.contentView.setTextViewText(R.id.status_text, "ownCloud Music Player"); // NULL POINTER
+ //mNotification.contentView.setTextViewText(R.id.status_text, getString(R.string.downloader_download_in_progress_content));
+
+
+ /// includes a pending intent in the notification showing the details view of the file
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
+ (int)System.currentTimeMillis(),
+ showDetailsIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+
+ //mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification);
+ String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
+ mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent);
+ startForeground(R.string.media_notif_ticker, mNotification);
+
+ }
+
+ /**
+ * Called when there's an error playing media.
+ *
+ * Warns the user about the error and resets the media player.
+ */
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra);
+
+ String message = getMessageForMediaError(this, what, extra);
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
+
+ processStopRequest(true);
+ return true;
+ }
+
+ /**
+ * Called by the system when another app tries to play some sound.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ if (focusChange > 0) {
+ // focus gain; check AudioManager.AUDIOFOCUS_* values
+ mAudioFocus = AudioFocus.FOCUS;
+ // restart media player with new focus settings
+ if (mState == State.PLAYING)
+ configAndStartMediaPlayer();
+
+ } else if (focusChange < 0) {
+ // focus loss; check AudioManager.AUDIOFOCUS_* values
+ boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange;
+ mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS;
+ // start/restart/pause media player with new focus settings
+ if (mPlayer != null && mPlayer.isPlaying())
+ configAndStartMediaPlayer();
+ }
+
+ }
+
+ /**
+ * Called when the service is finished for final clean-up.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ mState = State.STOPPED;
+ releaseResources(true);
+ giveUpAudioFocus();
+ }
+
+
+ /**
+ * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService.
+ */
+ @Override
+ public IBinder onBind(Intent arg) {
+ return mBinder;
+ }
+
+
+ /**
+ * Called when ALL the bound clients were onbound.
+ *
+ * The service is destroyed if playback stopped or paused
+ */
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (mState == State.PAUSED || mState == State.STOPPED) {
+ processStopRequest(false);
+ }
+ return false; // not accepting rebinding (default behaviour)
+ }
+
+
+ /**
+ * Accesses the current MediaPlayer instance in the service.
+ *
+ * To be handled carefully. Visibility is protected to be accessed only
+ *
+ * @return Current MediaPlayer instance handled by MediaService.
+ */
+ protected MediaPlayer getPlayer() {
+ return mPlayer;
+ }
+
+
+ /**
+ * Accesses the current OCFile loaded in the service.
+ *
+ * @return The current OCFile loaded in the service.
+ */
+ protected OCFile getCurrentFile() {
+ return mFile;
+ }
+
+
+ /**
+ * Accesses the current {@link State} of the MediaService.
+ *
+ * @return The current {@link State} of the MediaService.
+ */
+ protected State getState() {
+ return mState;
+ }
+
+
+ protected void setMediaContoller(MediaControlView mediaController) {
+ mMediaController = mediaController;
+ }
+
+ protected MediaControlView getMediaController() {
+ return mMediaController;
+ }
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.media;
+
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.media.MediaService.State;
+
+import android.accounts.Account;
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.os.Binder;
+import android.util.Log;
+import android.widget.MediaController;
+
+
+/**
+ * Binder allowing client components to perform operations on on the MediaPlayer managed by a MediaService instance.
+ *
+ * Provides the operations of {@link MediaController.MediaPlayerControl}, and an extra method to check if
+ * an {@link OCFile} instance is handled by the MediaService.
+ *
+ * @author David A. Velasco
+ */
+public class MediaServiceBinder extends Binder implements MediaController.MediaPlayerControl {
+
+ private static final String TAG = MediaServiceBinder.class.getSimpleName();
+ /**
+ * {@link MediaService} instance to access with the binder
+ */
+ private MediaService mService = null;
+
+ /**
+ * Public constructor
+ *
+ * @param service A {@link MediaService} instance to access with the binder
+ */
+ public MediaServiceBinder(MediaService service) {
+ if (service == null) {
+ throw new IllegalArgumentException("Argument 'service' can not be null");
+ }
+ mService = service;
+ }
+
+
+ public boolean isPlaying(OCFile mFile) {
+ return (mFile != null && mFile.equals(mService.getCurrentFile()));
+ }
+
+
+ @Override
+ public boolean canPause() {
+ return true;
+ }
+
+ @Override
+ public boolean canSeekBackward() {
+ return true;
+ }
+
+ @Override
+ public boolean canSeekForward() {
+ return true;
+ }
+
+ @Override
+ public int getBufferPercentage() {
+ MediaPlayer currentPlayer = mService.getPlayer();
+ if (currentPlayer != null) {
+ return 100;
+ // TODO update for streamed playback; add OnBufferUpdateListener in MediaService
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ MediaPlayer currentPlayer = mService.getPlayer();
+ if (currentPlayer != null) {
+ int pos = currentPlayer.getCurrentPosition();
+ return pos;
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getDuration() {
+ MediaPlayer currentPlayer = mService.getPlayer();
+ if (currentPlayer != null) {
+ int dur = currentPlayer.getDuration();
+ return dur;
+ } else {
+ return 0;
+ }
+ }
+
+
+ /**
+ * Reports if the MediaService is playing a file or not.
+ *
+ * Considers that the file is being played when it is in preparation because the expected
+ * client of this method is a {@link MediaController} , and we do not want that the 'play'
+ * button is shown when the file is being prepared by the MediaService.
+ */
+ @Override
+ public boolean isPlaying() {
+ MediaService.State currentState = mService.getState();
+ return (currentState == State.PLAYING || (currentState == State.PREPARING && mService.mPlayOnPrepared));
+ }
+
+
+ @Override
+ public void pause() {
+ Log.d(TAG, "Pausing through binder...");
+ mService.processPauseRequest();
+ }
+
+ @Override
+ public void seekTo(int pos) {
+ Log.d(TAG, "Seeking " + pos + " through binder...");
+ MediaPlayer currentPlayer = mService.getPlayer();
+ MediaService.State currentState = mService.getState();
+ if (currentPlayer != null && currentState != State.PREPARING && currentState != State.STOPPED) {
+ currentPlayer.seekTo(pos);
+ }
+ }
+
+ @Override
+ public void start() {
+ Log.d(TAG, "Starting through binder...");
+ mService.processPlayRequest(); // this will finish the service if there is no file preloaded to play
+ }
+
+ public void start(Account account, OCFile file, boolean playImmediately, int position) {
+ Log.d(TAG, "Loading and starting through binder...");
+ Intent i = new Intent(mService, MediaService.class);
+ i.putExtra(MediaService.EXTRA_ACCOUNT, account);
+ i.putExtra(MediaService.EXTRA_FILE, file);
+ i.putExtra(MediaService.EXTRA_PLAY_ON_LOAD, playImmediately);
+ i.putExtra(MediaService.EXTRA_START_POSITION, position);
+ i.setAction(MediaService.ACTION_PLAY_FILE);
+ mService.startService(i);
+ }
+
+
+ public void registerMediaController(MediaControlView mediaController) {
+ mService.setMediaContoller(mediaController);
+ }
+
+ public void unregisterMediaController(MediaControlView mediaController) {
+ if (mediaController != null && mediaController == mService.getMediaController()) {
+ mService.setMediaContoller(null);
+ }
+
+ }
+
+ public boolean isInPlaybackState() {
+ MediaService.State currentState = mService.getState();
+ return (currentState == MediaService.State.PLAYING || currentState == MediaService.State.PAUSED);
+ }
+
+}
+
+
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.network;
+
+import java.util.Collection;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
+
+public interface ProgressiveDataTransferer {
+
+ public void addDatatransferProgressListener (OnDatatransferProgressListener listener);
+
+ public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners);
+
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener);
+
+}
import org.apache.commons.httpclient.methods.PutMethod;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.network.ProgressiveDataTransferer;
import android.accounts.Account;
import android.util.Log;
File file = new File(getStoragePath());
raf = new RandomAccessFile(file, "r");
channel = raf.getChannel();
- ChunkFromFileChannelRequestEntity entity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file);
- entity.addOnDatatransferProgressListeners(getDataTransferListeners());
+ mEntity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file);
+ ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(getDataTransferListeners());
long offset = 0;
String uriPrefix = client.getBaseUri() + WebdavUtils.encodePath(getRemotePath()) + "-chunking-" + Math.abs((new Random()).nextInt(9000)+1000) + "-" ;
long chunkCount = (long) Math.ceil((double)file.length() / CHUNK_SIZE);
for (int chunkIndex = 0; chunkIndex < chunkCount ; chunkIndex++, offset += CHUNK_SIZE) {
mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex);
mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER);
- entity.setOffset(offset);
- mPutMethod.setRequestEntity(entity);
+ ((ChunkFromFileChannelRequestEntity)mEntity).setOffset(offset);
+ mPutMethod.setRequestEntity(mEntity);
status = client.executeMethod(mPutMethod);
client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
Log.d(TAG, "Upload of " + getStoragePath() + " to " + getRemotePath() + ", chunk index " + chunkIndex + ", count " + chunkCount + ", HTTP result status " + status);
public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
- mDataTransferListeners.add(listener);
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.add(listener);
+ }
}
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.remove(listener);
+ }
+ }
+
@Override
protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null;
}
fos.write(bytes, 0, readResult);
transferred += readResult;
- it = mDataTransferListeners.iterator();
- while (it.hasNext()) {
- it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName());
+ synchronized (mDataTransferListeners) {
+ it = mDataTransferListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName());
+ }
}
}
savedFile = true;
mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it
}
+
}
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.DavException;
-import com.owncloud.android.network.CertificateCombinedException;
+import android.util.Log;
+import com.owncloud.android.network.CertificateCombinedException;
/**
* The result of a remote operation required to an ownCloud server.
*
- * Provides a common classification of remote operation results for all the application.
+ * Provides a common classification of remote operation results for all the
+ * application.
*
* @author David A. Velasco
*/
public class RemoteOperationResult implements Serializable {
-
+
/** Generated - should be refreshed every time the class changes!! */
private static final long serialVersionUID = -7805531062432602444L;
+ private static final String TAG = "RemoteOperationResult";
-
- public enum ResultCode {
- OK,
- OK_SSL,
- OK_NO_SSL,
- UNHANDLED_HTTP_CODE,
- UNAUTHORIZED,
- FILE_NOT_FOUND,
- INSTANCE_NOT_CONFIGURED,
- UNKNOWN_ERROR,
- WRONG_CONNECTION,
- TIMEOUT,
- INCORRECT_ADDRESS,
- HOST_NOT_AVAILABLE,
- NO_NETWORK_CONNECTION,
- SSL_ERROR,
- SSL_RECOVERABLE_PEER_UNVERIFIED,
- BAD_OC_VERSION,
- CANCELLED,
- INVALID_LOCAL_FILE_NAME,
- INVALID_OVERWRITE,
- CONFLICT,
- SYNC_CONFLICT,
- LOCAL_STORAGE_FULL,
- LOCAL_STORAGE_NOT_MOVED,
- LOCAL_STORAGE_NOT_COPIED
+ public enum ResultCode {
+ OK, OK_SSL, OK_NO_SSL, UNHANDLED_HTTP_CODE, UNAUTHORIZED, FILE_NOT_FOUND, INSTANCE_NOT_CONFIGURED, UNKNOWN_ERROR, WRONG_CONNECTION, TIMEOUT, INCORRECT_ADDRESS, HOST_NOT_AVAILABLE, NO_NETWORK_CONNECTION, SSL_ERROR, SSL_RECOVERABLE_PEER_UNVERIFIED, BAD_OC_VERSION, CANCELLED, INVALID_LOCAL_FILE_NAME, INVALID_OVERWRITE, CONFLICT, SYNC_CONFLICT, LOCAL_STORAGE_FULL, LOCAL_STORAGE_NOT_MOVED, LOCAL_STORAGE_NOT_COPIED, QUOTA_EXCEEDED
}
private boolean mSuccess = false;
private int mHttpCode = -1;
private Exception mException = null;
private ResultCode mCode = ResultCode.UNKNOWN_ERROR;
-
+
public RemoteOperationResult(ResultCode code) {
mCode = code;
mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL);
}
-
+
public RemoteOperationResult(boolean success, int httpCode) {
- mSuccess = success;
+ mSuccess = success;
mHttpCode = httpCode;
if (success) {
mCode = ResultCode.OK;
-
+
} else if (httpCode > 0) {
switch (httpCode) {
- case HttpStatus.SC_UNAUTHORIZED:
- mCode = ResultCode.UNAUTHORIZED;
- break;
- case HttpStatus.SC_NOT_FOUND:
- mCode = ResultCode.FILE_NOT_FOUND;
- break;
- case HttpStatus.SC_INTERNAL_SERVER_ERROR:
- mCode = ResultCode.INSTANCE_NOT_CONFIGURED;
- break;
- case HttpStatus.SC_CONFLICT:
- mCode = ResultCode.CONFLICT;
- break;
- default:
- mCode = ResultCode.UNHANDLED_HTTP_CODE;
+ case HttpStatus.SC_UNAUTHORIZED:
+ mCode = ResultCode.UNAUTHORIZED;
+ break;
+ case HttpStatus.SC_NOT_FOUND:
+ mCode = ResultCode.FILE_NOT_FOUND;
+ break;
+ case HttpStatus.SC_INTERNAL_SERVER_ERROR:
+ mCode = ResultCode.INSTANCE_NOT_CONFIGURED;
+ break;
+ case HttpStatus.SC_CONFLICT:
+ mCode = ResultCode.CONFLICT;
+ break;
+ case HttpStatus.SC_INSUFFICIENT_STORAGE:
+ mCode = ResultCode.QUOTA_EXCEEDED;
+ break;
+ default:
+ mCode = ResultCode.UNHANDLED_HTTP_CODE;
+ Log.d(TAG, "RemoteOperationResult has prcessed UNHANDLED_HTTP_CODE: " + httpCode);
}
}
}
-
+
public RemoteOperationResult(Exception e) {
- mException = e;
-
+ mException = e;
+
if (e instanceof OperationCancelledException) {
mCode = ResultCode.CANCELLED;
-
- } else if (e instanceof SocketException) {
+
+ } else if (e instanceof SocketException) {
mCode = ResultCode.WRONG_CONNECTION;
-
+
} else if (e instanceof SocketTimeoutException) {
mCode = ResultCode.TIMEOUT;
-
+
} else if (e instanceof ConnectTimeoutException) {
mCode = ResultCode.TIMEOUT;
-
+
} else if (e instanceof MalformedURLException) {
mCode = ResultCode.INCORRECT_ADDRESS;
-
+
} else if (e instanceof UnknownHostException) {
mCode = ResultCode.HOST_NOT_AVAILABLE;
-
+
} else if (e instanceof SSLException || e instanceof RuntimeException) {
CertificateCombinedException se = getCertificateCombinedException(e);
if (se != null) {
mException = se;
- if (se.isRecoverable()) {
+ if (se.isRecoverable()) {
mCode = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED;
}
-
- } else {
+ } else if (e instanceof RuntimeException) {
+ mCode = ResultCode.HOST_NOT_AVAILABLE;
+
+ } else {
mCode = ResultCode.SSL_ERROR;
}
-
+
} else {
mCode = ResultCode.UNKNOWN_ERROR;
}
-
+
}
-
-
+
public boolean isSuccess() {
return mSuccess;
}
-
+
public boolean isCancelled() {
return mCode == ResultCode.CANCELLED;
}
-
+
public int getHttpCode() {
return mHttpCode;
}
-
+
public ResultCode getCode() {
return mCode;
}
-
+
public Exception getException() {
return mException;
}
public boolean isSslRecoverableException() {
return mCode == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED;
}
-
+
private CertificateCombinedException getCertificateCombinedException(Exception e) {
CertificateCombinedException result = null;
if (e instanceof CertificateCombinedException) {
- return (CertificateCombinedException)e;
+ return (CertificateCombinedException) e;
}
Throwable cause = mException.getCause();
Throwable previousCause = null;
cause = cause.getCause();
}
if (cause != null && cause instanceof CertificateCombinedException) {
- result = (CertificateCombinedException)cause;
+ result = (CertificateCombinedException) cause;
}
return result;
}
-
-
+
public String getLogMessage() {
-
+
if (mException != null) {
if (mException instanceof OperationCancelledException) {
return "Operation cancelled by the caller";
-
- } else if (mException instanceof SocketException) {
+
+ } else if (mException instanceof SocketException) {
return "Socket exception";
-
+
} else if (mException instanceof SocketTimeoutException) {
return "Socket timeout exception";
-
+
} else if (mException instanceof ConnectTimeoutException) {
return "Connect timeout exception";
-
+
} else if (mException instanceof MalformedURLException) {
return "Malformed URL exception";
-
+
} else if (mException instanceof UnknownHostException) {
return "Unknown host exception";
-
+
} else if (mException instanceof CertificateCombinedException) {
if (((CertificateCombinedException) mException).isRecoverable())
return "SSL recoverable exception";
else
return "SSL exception";
-
+
} else if (mException instanceof SSLException) {
return "SSL exception";
return "Unexpected exception";
}
}
-
+
if (mCode == ResultCode.INSTANCE_NOT_CONFIGURED) {
return "The ownCloud server is not configured!";
-
+
} else if (mCode == ResultCode.NO_NETWORK_CONNECTION) {
return "No network connection";
-
+
} else if (mCode == ResultCode.BAD_OC_VERSION) {
return "No valid ownCloud version was found at the server";
-
+
} else if (mCode == ResultCode.LOCAL_STORAGE_FULL) {
return "Local storage full";
-
+
} else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) {
return "Error while moving file to final directory";
}
-
- return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess()?"success":"fail") + ")";
-
+
+ return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess() ? "success" : "fail") + ")";
+
}
}
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.http.HttpStatus;
+import android.accounts.Account;
+import android.util.Log;
+
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.network.ProgressiveDataTransferer;
import com.owncloud.android.operations.RemoteOperation;
import com.owncloud.android.operations.RemoteOperationResult;
import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
import eu.alefzero.webdav.OnDatatransferProgressListener;
import eu.alefzero.webdav.WebdavClient;
import eu.alefzero.webdav.WebdavUtils;
-import android.accounts.Account;
-import android.util.Log;
/**
* Remote operation performing the upload of a file to an ownCloud server
* @author David A. Velasco
*/
public class UploadFileOperation extends RemoteOperation {
-
+
private static final String TAG = UploadFileOperation.class.getSimpleName();
private Account mAccount;
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+ protected RequestEntity mEntity = null;
+
public UploadFileOperation( Account account,
OCFile file,
throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
if (file == null)
throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
- if (file.getStoragePath() == null || file.getStoragePath().length() <= 0 || !(new File(file.getStoragePath()).exists())) {
- throw new IllegalArgumentException("Illegal file in UploadFileOperation; storage path invalid or file not found: " + file.getStoragePath());
+ if (file.getStoragePath() == null || file.getStoragePath().length() <= 0
+ || !(new File(file.getStoragePath()).exists())) {
+ throw new IllegalArgumentException(
+ "Illegal file in UploadFileOperation; storage path invalid or file not found: "
+ + file.getStoragePath());
}
-
+
mAccount = account;
mFile = file;
mRemotePath = file.getRemotePath();
mOriginalFileName = mFile.getFileName();
}
-
public Account getAccount() {
return mAccount;
}
-
+
public String getFileName() {
return mOriginalFileName;
}
-
+
public OCFile getFile() {
return mFile;
}
-
+
public OCFile getOldFile() {
- return mOldFile;
+ return mOldFile;
}
-
+
public String getOriginalStoragePath() {
return mOriginalStoragePath;
}
-
+
public String getStoragePath() {
return mFile.getStoragePath();
}
public String getRemotePath() {
- return mFile.getRemotePath();
+ return mFile.getRemotePath();
}
public String getMimeType() {
return mFile.getMimetype();
}
-
+
public boolean isInstant() {
return mIsInstant;
}
public boolean isRemoteFolderToBeCreated() {
return mRemoteFolderToBeCreated;
}
-
+
public void setRemoteFolderToBeCreated() {
mRemoteFolderToBeCreated = true;
}
public boolean getForceOverwrite() {
return mForceOverwrite;
}
-
+
public boolean wasRenamed() {
return mWasRenamed;
}
-
+
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
return mDataTransferListeners;
}
public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
- mDataTransferListeners.add(listener);
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.add(listener);
+ }
+ if (mEntity != null) {
+ ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
+ }
}
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.remove(listener);
+ }
+ if (mEntity != null) {
+ ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
+ }
+ }
+
@Override
protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null;
boolean localCopyPassed = false, nameCheckPassed = false;
File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
try {
- /// rename the file to upload, if necessary
+ // / rename the file to upload, if necessary
if (!mForceOverwrite) {
String remotePath = getAvailableRemotePath(client, mRemotePath);
mWasRenamed = !remotePath.equals(mRemotePath);
if (mWasRenamed) {
- createNewOCFile(remotePath);
+ createNewOCFile(remotePath);
}
}
nameCheckPassed = true;
-
- String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); /// not before getAvailableRemotePath() !!!
+
+ String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // /
+ // not
+ // before
+ // getAvailableRemotePath()
+ // !!!
expectedFile = new File(expectedPath);
-
- /// check location of local file; if not the expected, copy to a temporal file before upload (if COPY is the expected behaviour)
+
+ // / check location of local file; if not the expected, copy to a
+ // temporal file before upload (if COPY is the expected behaviour)
if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
- return result; // error condition when the file should be copied
-
+ return result; // error condition when the file should be
+ // copied
+
} else {
String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
mFile.setStoragePath(temporalPath);
temporalFile = new File(temporalPath);
- if (!mOriginalStoragePath.equals(temporalPath)) { // preventing weird but possible situation
+ if (!mOriginalStoragePath.equals(temporalPath)) { // preventing
+ // weird
+ // but
+ // possible
+ // situation
InputStream in = null;
OutputStream out = null;
try {
temporalFile.createNewFile();
if (!temporalFile.isFile()) {
throw new IOException("Unexpected error: target file could not be created");
- }
+ }
in = new FileInputStream(originalFile);
out = new FileOutputStream(temporalFile);
byte[] buf = new byte[1024];
int len;
- while ((len = in.read(buf)) > 0){
+ while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
-
+
} catch (Exception e) {
result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
return result;
-
+
} finally {
try {
- if (in != null) in.close();
+ if (in != null)
+ in.close();
} catch (Exception e) {
- Log.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e);
+ Log.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath
+ + " (ignoring)", e);
}
try {
- if (out != null) out.close();
+ if (out != null)
+ out.close();
} catch (Exception e) {
- Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+ Log.d(TAG, "Weird exception while closing output stream for " + expectedPath
+ + " (ignoring)", e);
}
}
}
}
}
localCopyPassed = true;
-
- /// perform the upload
- synchronized(mCancellationRequested) {
+
+ // / perform the upload
+ synchronized (mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
} else {
}
}
int status = uploadFile(client);
-
-
- /// move local temporal file or original file to its corresponding location in the ownCloud local folder
+
+ // / move local temporal file or original file to its corresponding
+ // location in the ownCloud local folder
if (isSuccess(status)) {
if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
mFile.setStoragePath(null);
-
+
} else {
mFile.setStoragePath(expectedPath);
File fileToMove = null;
- if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY ; see where temporalFile was set
+ if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
+ // ; see where temporalFile was
+ // set
fileToMove = temporalFile;
- } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
+ } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
fileToMove = originalFile;
}
if (!expectedFile.equals(fileToMove)) {
expectedFolder.mkdirs();
if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
mFile.setStoragePath(null); // forget the local file
- // by now, treat this as a success; the file was uploaded; the user won't like that the local file is not linked, but this should be a veeery rare fail;
- // the best option could be show a warning message (but not a fail)
- //result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
- //return result;
+ // by now, treat this as a success; the file was
+ // uploaded; the user won't like that the local file
+ // is not linked, but this should be a very rare
+ // fail;
+ // the best option could be show a warning message
+ // (but not a fail)
+ // result = new
+ // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
+ // return result;
}
}
- }
+ }
}
-
+
result = new RemoteOperationResult(isSuccess(status), status);
-
-
+
} catch (Exception e) {
// TODO something cleaner with cancellations
if (mCancellationRequested.get()) {
} else {
result = new RemoteOperationResult(e);
}
-
-
+
} finally {
if (temporalFile != null && !originalFile.equals(temporalFile)) {
temporalFile.delete();
}
if (result.isSuccess()) {
Log.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
-
+
} else {
if (result.getException() != null) {
String complement = "";
if (!nameCheckPassed) {
complement = " (while checking file existence in server)";
} else if (!localCopyPassed) {
- complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")";
+ complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name)
+ + ")";
}
- Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
+ Log.e(TAG,
+ "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()
+ + complement, result.getException());
} else {
- Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+ Log.e(TAG,
+ "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
}
}
}
-
+
return result;
}
-
private void createNewOCFile(String newRemotePath) {
// a new OCFile instance must be created for a new remote path
OCFile newFile = new OCFile(newRemotePath);
mFile = newFile;
}
-
public boolean isSuccess(int status) {
return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT));
}
-
-
+
protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException {
int status = -1;
try {
File f = new File(mFile.getStoragePath());
- FileRequestEntity entity = new FileRequestEntity(f, getMimeType());
- entity.addOnDatatransferProgressListeners(mDataTransferListeners);
- mPutMethod.setRequestEntity(entity);
+ mEntity = new FileRequestEntity(f, getMimeType());
+ synchronized (mDataTransferListeners) {
+ ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(mDataTransferListeners);
+ }
+ mPutMethod.setRequestEntity(mEntity);
status = client.executeMethod(mPutMethod);
client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
-
+
} finally {
- mPutMethod.releaseConnection(); // let the connection available for other methods
+ mPutMethod.releaseConnection(); // let the connection available for
+ // other methods
}
return status;
}
-
+
/**
- * Checks if remotePath does not exist in the server and returns it, or adds a suffix to it in order to avoid the server
- * file is overwritten.
+ * Checks if remotePath does not exist in the server and returns it, or adds
+ * a suffix to it in order to avoid the server file is overwritten.
*
* @param string
* @return
if (!check) {
return remotePath;
}
-
+
int pos = remotePath.lastIndexOf(".");
String suffix = "";
String extension = "";
if (pos >= 0) {
- extension = remotePath.substring(pos+1);
+ extension = remotePath.substring(pos + 1);
remotePath = remotePath.substring(0, pos);
}
int count = 2;
count++;
} while (check);
- if (pos >=0) {
+ if (pos >= 0) {
return remotePath + suffix + "." + extension;
} else {
return remotePath + suffix;
}
}
-
public void cancel() {
- synchronized(mCancellationRequested) {
+ synchronized (mCancellationRequested) {
mCancellationRequested.set(true);
if (mPutMethod != null)
mPutMethod.abort();
}
}
-
}
--- /dev/null
+package com.owncloud.android.ui.activity;\r
+\r
+import android.app.Activity;\r
+import android.os.Bundle;\r
+import android.view.View;\r
+import android.view.View.OnClickListener;\r
+import android.widget.Button;\r
+import android.widget.TextView;\r
+\r
+import com.owncloud.android.R;\r
+\r
+/**\r
+ * This Activity is used to display a detail message for failed uploads\r
+ * \r
+ * The entry-point for this activity is the 'Failed upload Notification"\r
+ * \r
+ * \r
+ * @author andomaex / Matthias Baumann\r
+ * \r
+ * This program is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation, either version 3 of the License, or (at\r
+ * your option) any later version.\r
+ * \r
+ * This program is distributed in the hope that it will be useful, but\r
+ * WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+ * General Public License for more de/\r
+ */\r
+public class FailedUploadActivity extends Activity {\r
+\r
+ public static final String MESSAGE = "message";\r
+\r
+ @Override\r
+ public void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+ setContentView(R.layout.failed_upload_message_view);\r
+ String message = getIntent().getStringExtra(MESSAGE);\r
+ TextView textView = (TextView) findViewById(R.id.faild_upload_message);\r
+ textView.setText(message);\r
+ Button close_button = (Button) findViewById(R.id.failed_uploadactivity_close_button);\r
+ close_button.setOnClickListener(new OnClickListener() {\r
+ @Override\r
+ public void onClick(View v) {\r
+ finish();\r
+ }\r
+ });\r
+ }\r
+}\r
import android.accounts.Account;\r
import android.app.Dialog;\r
import android.app.ProgressDialog;\r
+import android.content.BroadcastReceiver;\r
import android.content.ComponentName;\r
import android.content.Context;\r
import android.content.Intent;\r
+import android.content.IntentFilter;\r
import android.content.ServiceConnection;\r
import android.content.res.Configuration;\r
import android.os.Bundle;\r
import android.os.IBinder;\r
+import android.support.v4.app.Fragment;\r
import android.support.v4.app.FragmentTransaction;\r
import android.util.Log;\r
\r
import com.actionbarsherlock.app.ActionBar;\r
import com.actionbarsherlock.app.SherlockFragmentActivity;\r
import com.actionbarsherlock.view.MenuItem;\r
+import com.owncloud.android.datamodel.FileDataStorageManager;\r
import com.owncloud.android.datamodel.OCFile;\r
import com.owncloud.android.files.services.FileDownloader;\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
import com.owncloud.android.files.services.FileUploader;\r
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
import com.owncloud.android.ui.fragment.FileDetailFragment;\r
+import com.owncloud.android.ui.fragment.FileFragment;\r
+import com.owncloud.android.ui.preview.PreviewMediaFragment;\r
\r
+import com.owncloud.android.AccountUtils;\r
import com.owncloud.android.R;\r
\r
/**\r
* on.\r
* \r
* @author Bartek Przybylski\r
- * \r
+ * @author David A. Velasco\r
*/\r
-public class FileDetailActivity extends SherlockFragmentActivity implements FileDetailFragment.ContainerActivity {\r
+public class FileDetailActivity extends SherlockFragmentActivity implements FileFragment.ContainerActivity {\r
\r
public static final int DIALOG_SHORT_WAIT = 0;\r
\r
public static final String TAG = FileDetailActivity.class.getSimpleName();\r
\r
+ public static final String EXTRA_MODE = "MODE";\r
+ public static final int MODE_DETAILS = 0;\r
+ public static final int MODE_PREVIEW = 1;\r
+\r
+ public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";\r
+ \r
private boolean mConfigurationChangedToLandscape = false;\r
private FileDownloaderBinder mDownloaderBinder = null;\r
private ServiceConnection mDownloadConnection, mUploadConnection = null;\r
private FileUploaderBinder mUploaderBinder = null;\r
+ private boolean mWaitingToPreview;\r
+ \r
+ private OCFile mFile;\r
+ private Account mAccount;\r
\r
+ private FileDataStorageManager mStorageManager;\r
+ private DownloadFinishReceiver mDownloadFinishReceiver;\r
+ \r
\r
@Override\r
protected void onCreate(Bundle savedInstanceState) {\r
super.onCreate(savedInstanceState);\r
\r
+ mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);\r
+ mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);\r
+ mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());\r
+ \r
// check if configuration changed to large-land ; for a tablet being changed from portrait to landscape when in FileDetailActivity \r
Configuration conf = getResources().getConfiguration();\r
mConfigurationChangedToLandscape = (conf.orientation == Configuration.ORIENTATION_LANDSCAPE && \r
);\r
\r
if (!mConfigurationChangedToLandscape) {\r
- mDownloadConnection = new DetailsServiceConnection();\r
- bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);\r
- mUploadConnection = new DetailsServiceConnection();\r
- bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);\r
- \r
setContentView(R.layout.file_activity_details);\r
\r
ActionBar actionBar = getSupportActionBar();\r
actionBar.setDisplayHomeAsUpEnabled(true);\r
\r
- OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);\r
- Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);\r
- FileDetailFragment mFileDetail = new FileDetailFragment(file, account);\r
- \r
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();\r
- ft.replace(R.id.fragment, mFileDetail, FileDetailFragment.FTAG);\r
- ft.commit();\r
+ if (savedInstanceState == null) {\r
+ mWaitingToPreview = false;\r
+ createChildFragment();\r
+ } else {\r
+ mWaitingToPreview = savedInstanceState.getBoolean(KEY_WAITING_TO_PREVIEW);\r
+ }\r
+ \r
+ mDownloadConnection = new DetailsServiceConnection();\r
+ bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);\r
+ mUploadConnection = new DetailsServiceConnection();\r
+ bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);\r
+ \r
\r
} else {\r
backToDisplayActivity(); // the 'back' won't be effective until this.onStart() and this.onResume() are completed;\r
\r
\r
}\r
+\r
+ /**\r
+ * Creates the proper fragment depending upon the state of the handled {@link OCFile} and\r
+ * the requested {@link Intent}.\r
+ */\r
+ private void createChildFragment() {\r
+ int mode = getIntent().getIntExtra(EXTRA_MODE, MODE_PREVIEW); \r
+ \r
+ Fragment newFragment = null;\r
+ if (PreviewMediaFragment.canBePreviewed(mFile) && mode == MODE_PREVIEW) {\r
+ if (mFile.isDown()) {\r
+ newFragment = new PreviewMediaFragment(mFile, mAccount);\r
+ \r
+ } else {\r
+ newFragment = new FileDetailFragment(mFile, mAccount);\r
+ mWaitingToPreview = true;\r
+ }\r
+ \r
+ } else {\r
+ newFragment = new FileDetailFragment(mFile, mAccount);\r
+ }\r
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();\r
+ ft.replace(R.id.fragment, newFragment, FileDetailFragment.FTAG);\r
+ ft.commit();\r
+ }\r
+ \r
+\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle outState) {\r
+ super.onSaveInstanceState(outState);\r
+ outState.putBoolean(KEY_WAITING_TO_PREVIEW, mWaitingToPreview);\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public void onPause() {\r
+ super.onPause();\r
+ if (mDownloadFinishReceiver != null) {\r
+ unregisterReceiver(mDownloadFinishReceiver);\r
+ mDownloadFinishReceiver = null;\r
+ }\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public void onResume() {\r
+ super.onResume();\r
+ if (!mConfigurationChangedToLandscape) {\r
+ // TODO this is probably unnecessary\r
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (fragment != null && fragment instanceof FileDetailFragment) {\r
+ ((FileDetailFragment) fragment).updateFileDetails(false, false);\r
+ }\r
+ }\r
+ // Listen for download messages\r
+ IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE);\r
+ downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
+ mDownloadFinishReceiver = new DownloadFinishReceiver();\r
+ registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);\r
+ }\r
\r
\r
/** Defines callbacks for service binding, passed to bindService() */\r
\r
@Override\r
public void onServiceConnected(ComponentName component, IBinder service) {\r
+ \r
if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {\r
Log.d(TAG, "Download service connected");\r
mDownloaderBinder = (FileDownloaderBinder) service;\r
+ if (mWaitingToPreview) {\r
+ requestForDownload();\r
+ }\r
+ \r
} else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {\r
Log.d(TAG, "Upload service connected");\r
mUploaderBinder = (FileUploaderBinder) service;\r
} else {\r
return;\r
}\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fragment != null)\r
- fragment.updateFileDetails(false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())\r
+ \r
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ FileDetailFragment detailsFragment = (fragment instanceof FileDetailFragment) ? (FileDetailFragment) fragment : null;\r
+ if (detailsFragment != null) {\r
+ detailsFragment.listenForTransferProgress();\r
+ detailsFragment.updateFileDetails(mWaitingToPreview, false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())\r
+ }\r
}\r
\r
@Override\r
}\r
}; \r
\r
-\r
+ \r
@Override\r
public void onDestroy() {\r
super.onDestroy();\r
\r
\r
\r
- @Override\r
- protected void onResume() {\r
- \r
- super.onResume();\r
- if (!mConfigurationChangedToLandscape) { \r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- fragment.updateFileDetails(false);\r
- }\r
- }\r
- \r
-\r
private void backToDisplayActivity() {\r
Intent intent = new Intent(this, FileDisplayActivity.class);\r
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
- intent.putExtra(FileDetailFragment.EXTRA_FILE, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE));\r
- intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT));\r
+ OCFile targetFile = null;\r
+ if (mFile != null) {\r
+ targetFile = mStorageManager.getFileById(mFile.getParentId());\r
+ }\r
+ intent.putExtra(FileDetailFragment.EXTRA_FILE, targetFile);\r
+ intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);\r
startActivity(intent);\r
finish();\r
}\r
public FileUploaderBinder getFileUploaderBinder() {\r
return mUploaderBinder;\r
}\r
+\r
+\r
+ @Override\r
+ public void showFragmentWithDetails(OCFile file) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.fragment, new FileDetailFragment(file, mAccount), FileDetailFragment.FTAG); \r
+ transaction.commit();\r
+ }\r
+\r
+ \r
+ private void requestForDownload() {\r
+ if (!mDownloaderBinder.isDownloading(mAccount, mFile)) {\r
+ Intent i = new Intent(this, FileDownloader.class);\r
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);\r
+ i.putExtra(FileDownloader.EXTRA_FILE, mFile);\r
+ startService(i);\r
+ }\r
+ }\r
+\r
\r
+ /**\r
+ * Class waiting for broadcast events from the {@link FielDownloader} service.\r
+ * \r
+ * Updates the UI when a download is started or finished, provided that it is relevant for the\r
+ * current file.\r
+ */\r
+ private class DownloadFinishReceiver extends BroadcastReceiver {\r
+ @Override\r
+ public void onReceive(Context context, Intent intent) {\r
+ boolean sameAccount = isSameAccount(context, intent);\r
+ String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
+ boolean samePath = (mFile != null && mFile.getRemotePath().equals(downloadedRemotePath));\r
+ \r
+ if (sameAccount && samePath) {\r
+ updateChildFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));\r
+ }\r
+ \r
+ removeStickyBroadcast(intent);\r
+ }\r
+\r
+ private boolean isSameAccount(Context context, Intent intent) {\r
+ String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
+ return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name));\r
+ }\r
+ }\r
+\r
+\r
+ public void updateChildFragment(String downloadEvent, String downloadedRemotePath, boolean success) {\r
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (fragment != null && fragment instanceof FileDetailFragment) {\r
+ FileDetailFragment detailsFragment = (FileDetailFragment) fragment;\r
+ OCFile fileInFragment = detailsFragment.getFile();\r
+ if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {\r
+ // this never should happen; fileInFragment should be always equals to mFile, that was compared to downloadedRemotePath in DownloadReceiver \r
+ mWaitingToPreview = false;\r
+ \r
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) {\r
+ // grants that the progress bar is updated\r
+ detailsFragment.listenForTransferProgress();\r
+ detailsFragment.updateFileDetails(true, false);\r
+ \r
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {\r
+ // refresh the details fragment \r
+ if (success && mWaitingToPreview) {\r
+ mFile = mStorageManager.getFileById(mFile.getFileId()); // update the file from database, for the local storage path\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.fragment, new PreviewMediaFragment(mFile, mAccount), FileDetailFragment.FTAG);\r
+ transaction.commit();\r
+ mWaitingToPreview = false;\r
+ \r
+ } else {\r
+ detailsFragment.updateFileDetails(false, (success));\r
+ // TODO error message if !success ¿?\r
+ }\r
+ }\r
+ } // TODO else if (fragment != null && fragment )\r
+ \r
+ \r
+ }\r
+\r
}\r
*\r
* This program is free software: you can redistribute it and/or modify\r
* it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
+ * the Free Software Foundation, either version 3 of the License, or\r
* (at your option) any later version.\r
*\r
* This program is distributed in the hope that it will be useful,\r
import android.os.IBinder;\r
import android.preference.PreferenceManager;\r
import android.provider.MediaStore;\r
+import android.support.v4.app.Fragment;\r
import android.support.v4.app.FragmentTransaction;\r
import android.util.Log;\r
import android.view.View;\r
import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;\r
import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
import com.owncloud.android.ui.fragment.FileDetailFragment;\r
+import com.owncloud.android.ui.fragment.FileFragment;\r
import com.owncloud.android.ui.fragment.OCFileListFragment;\r
+import com.owncloud.android.ui.preview.PreviewImageActivity;\r
+import com.owncloud.android.ui.preview.PreviewImageFragment;\r
+import com.owncloud.android.ui.preview.PreviewMediaFragment;\r
\r
import com.owncloud.android.R;\r
import eu.alefzero.webdav.WebdavClient;\r
* Displays, what files the user has available in his ownCloud.\r
* \r
* @author Bartek Przybylski\r
- * \r
+ * @author David A. Velasco\r
*/\r
\r
public class FileDisplayActivity extends SherlockFragmentActivity implements\r
\r
private static final String TAG = "FileDisplayActivity";\r
\r
- private static int[] mMenuIdentifiersToPatch = {R.id.about_app};\r
+ private static int[] mMenuIdentifiersToPatch = {R.id.action_about_app};\r
+ \r
+ private OCFile mWaitingToPreview;\r
+ private Handler mHandler;\r
+\r
\r
@Override\r
public void onCreate(Bundle savedInstanceState) {\r
if(savedInstanceState != null) {\r
// TODO - test if savedInstanceState should take precedence over file in the intent ALWAYS (now), NEVER, or SOME TIMES\r
mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);\r
+ mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW);\r
+\r
+ } else {\r
+ mWaitingToPreview = null;\r
}\r
\r
if (!AccountUtils.accountsAreSetup(this)) {\r
mDualPane = (findViewById(R.id.file_details_container) != null);\r
if (mDualPane) {\r
initFileDetailsInDualPane();\r
+ } else {\r
+ // quick patchES to fix problem in turn from landscape to portrait, when a file is selected in the right pane\r
+ // TODO serious refactorization in activities and fragments providing file browsing and handling \r
+ if (mCurrentFile != null) {\r
+ onFileClick(mCurrentFile);\r
+ mCurrentFile = null;\r
+ }\r
+ Fragment rightPanel = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (rightPanel != null) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.remove(rightPanel);\r
+ transaction.commit();\r
+ }\r
}\r
\r
// Action bar setup\r
startActivity(intent); // the new activity won't be created until this.onStart() and this.onResume() are finished;\r
}\r
\r
-\r
+ \r
/**\r
* Load of state dependent of the existence of an ownCloud account\r
*/\r
if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) {\r
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
if (mCurrentFile != null) {\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); // empty FileDetailFragment\r
+ if (PreviewMediaFragment.canBePreviewed(mCurrentFile)) {\r
+ if (mCurrentFile.isDown()) {\r
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ } else {\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ mWaitingToPreview = mCurrentFile;\r
+ }\r
+ } else {\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ }\r
mCurrentFile = null;\r
+ \r
} else {\r
transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment\r
}\r
@Override\r
public boolean onCreateOptionsMenu(Menu menu) {\r
MenuInflater inflater = getSherlock().getMenuInflater();\r
- inflater.inflate(R.menu.menu, menu);\r
+ inflater.inflate(R.menu.main_menu, menu);\r
\r
patchHiddenAccents(menu);\r
\r
public boolean onOptionsItemSelected(MenuItem item) {\r
boolean retval = true;\r
switch (item.getItemId()) {\r
- case R.id.createDirectoryItem: {\r
- //showDialog(DIALOG_CREATE_DIR);\r
+ case R.id.action_create_dir: {\r
EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.uploader_info_dirname), "", this);\r
dialog.show(getSupportFragmentManager(), "createdirdialog");\r
+ break;\r
}\r
- case R.id.startSync: {\r
+ case R.id.action_sync_account: {\r
startSynchronization();\r
break;\r
}\r
startActivity(settingsIntent);\r
break;\r
}\r
- case R.id.about_app : {\r
+ case R.id.action_about_app: {\r
showDialog(DIALOG_ABOUT_APP);\r
break;\r
}\r
\r
if (mDualPane) {\r
// Resets the FileDetailsFragment on Tablets so that it always displays\r
- FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fileDetails != null && !fileDetails.isEmpty()) {\r
+ Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) {\r
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.remove(fileDetails);\r
- transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment \r
transaction.commit();\r
}\r
}\r
super.onSaveInstanceState(outState);\r
outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir);\r
if (mDualPane) {\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ FileFragment fragment = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
if (fragment != null) {\r
- OCFile file = fragment.getDisplayedFile();\r
+ OCFile file = fragment.getFile();\r
if (file != null) {\r
outState.putParcelable(FileDetailFragment.EXTRA_FILE, file);\r
}\r
}\r
}\r
+ outState.putParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview);\r
Log.d(getClass().toString(), "onSaveInstanceState() end");\r
}\r
\r
@Override\r
- protected void onResume() {\r
+ public void onResume() {\r
Log.d(getClass().toString(), "onResume() start");\r
super.onResume();\r
\r
registerReceiver(mUploadFinishReceiver, uploadIntentFilter);\r
\r
// Listen for download messages\r
- IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
+ IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE);\r
+ downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
mDownloadFinishReceiver = new DownloadFinishReceiver();\r
registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);\r
\r
\r
\r
@Override\r
- protected void onPause() {\r
+ public void onPause() {\r
Log.d(getClass().toString(), "onPause() start");\r
super.onPause();\r
if (mSyncBroadcastReceiver != null) {\r
break;\r
}\r
case DIALOG_CHOOSE_UPLOAD_SOURCE: {\r
- final String [] items = { getString(R.string.actionbar_upload_files), \r
- getString(R.string.actionbar_upload_from_apps) }; \r
+ final String[] items = { getString(R.string.actionbar_upload_files),\r
+ getString(R.string.actionbar_upload_from_apps), \r
+ getString(R.string.actionbar_failed_instant_upload) };\r
builder = new AlertDialog.Builder(this);\r
builder.setTitle(R.string.actionbar_upload);\r
builder.setItems(items, new DialogInterface.OnClickListener() {\r
public void onClick(DialogInterface dialog, int item) {\r
if (item == 0) {\r
- //if (!mDualPane) { \r
- Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);\r
- action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));\r
- startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);\r
- //} else {\r
- // TODO create and handle new fragment LocalFileListFragment\r
- //}\r
+ // if (!mDualPane) {\r
+ Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);\r
+ action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT,\r
+ AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));\r
+ startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);\r
+ // } else {\r
+ // TODO create and handle new fragment\r
+ // LocalFileListFragment\r
+ // }\r
} else if (item == 1) {\r
Intent action = new Intent(Intent.ACTION_GET_CONTENT);\r
- action = action.setType("*/*")\r
- .addCategory(Intent.CATEGORY_OPENABLE);\r
- startActivityForResult(\r
- Intent.createChooser(action, getString(R.string.upload_chooser_title)),\r
+ action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE);\r
+ startActivityForResult(Intent.createChooser(action, getString(R.string.upload_chooser_title)),\r
ACTION_SELECT_CONTENT_FROM_APPS);\r
+ } else if (item == 2) {\r
+ Account account = AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this);\r
+ Intent action = new Intent(FileDisplayActivity.this, InstantUploadActivity.class);\r
+ action.putExtra(FileUploader.KEY_ACCOUNT, account);\r
+ startActivity(action);\r
}\r
}\r
});\r
\r
\r
/**\r
- * Once the file download has finished -> update view\r
+ * Class waiting for broadcast events from the {@link FielDownloader} service.\r
+ * \r
+ * Updates the UI when a download is started or finished, provided that it is relevant for the\r
+ * current folder.\r
*/\r
private class DownloadFinishReceiver extends BroadcastReceiver {\r
@Override\r
public void onReceive(Context context, Intent intent) {\r
+ boolean sameAccount = isSameAccount(context, intent);\r
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
- String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
- boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);\r
- boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
+ boolean isDescendant = isDescendant(downloadedRemotePath);\r
+ \r
if (sameAccount && isDescendant) {\r
- OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) { \r
- fileListFragment.listDirectory();\r
+ updateLeftPanel();\r
+ if (mDualPane) {\r
+ updateRightPanel(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));\r
}\r
}\r
+ \r
+ removeStickyBroadcast(intent);\r
+ }\r
+\r
+ private boolean isDescendant(String downloadedRemotePath) {\r
+ return (mCurrentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
+ }\r
+\r
+ private boolean isSameAccount(Context context, Intent intent) {\r
+ String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
+ return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name));\r
}\r
}\r
\r
\r
- \r
- \r
+ protected void updateLeftPanel() {\r
+ OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
+ if (fileListFragment != null) { \r
+ fileListFragment.listDirectory();\r
+ }\r
+ }\r
+\r
+ protected void updateRightPanel(String downloadEvent, String downloadedRemotePath, boolean success) {\r
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath));\r
+ if (fragment != null && fragment instanceof FileDetailFragment) {\r
+ FileDetailFragment detailsFragment = (FileDetailFragment) fragment;\r
+ OCFile fileInFragment = detailsFragment.getFile();\r
+ if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {\r
+ // the user browsed to other file ; forget the automatic preview \r
+ mWaitingToPreview = null;\r
+ \r
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) {\r
+ // grant that the right panel updates the progress bar\r
+ detailsFragment.listenForTransferProgress();\r
+ detailsFragment.updateFileDetails(true, false);\r
+ \r
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {\r
+ // update the right panel \r
+ if (success && waitedPreview) {\r
+ mWaitingToPreview = mStorageManager.getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mWaitingToPreview, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ transaction.commit();\r
+ mWaitingToPreview = null;\r
+ \r
+ } else {\r
+ detailsFragment.updateFileDetails(false, (success));\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
/**\r
* {@inheritDoc}\r
*/\r
return mStorageManager;\r
}\r
\r
- \r
+\r
/**\r
* {@inheritDoc}\r
*/\r
\r
if (mDualPane) {\r
// Resets the FileDetailsFragment on Tablets so that it always displays\r
- FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fileDetails != null && !fileDetails.isEmpty()) {\r
+ Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) {\r
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.remove(fileDetails);\r
- transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment \r
transaction.commit();\r
}\r
}\r
*/\r
@Override\r
public void onFileClick(OCFile file) {\r
- \r
- // If we are on a large device -> update fragment\r
+ if (file != null && PreviewImageFragment.canBePreviewed(file)) {\r
+ // preview image - it handles the download, if needed\r
+ startPreviewImage(file);\r
+ \r
+ } else if (file != null && PreviewMediaFragment.canBePreviewed(file)) {\r
+ if (file.isDown()) {\r
+ // general preview\r
+ startMediaPreview(file);\r
+ \r
+ } else {\r
+ // automatic download, preview on finish\r
+ startDownloadForPreview(file);\r
+ \r
+ }\r
+ } else {\r
+ // details view\r
+ startDetails(file);\r
+ }\r
+ }\r
+\r
+ private void startPreviewImage(OCFile file) {\r
+ Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ startActivity(showDetailsIntent);\r
+ }\r
+ \r
+ private void startMediaPreview(OCFile file) {\r
if (mDualPane) {\r
- // buttons in the details view are problematic when trying to reuse an existing fragment; create always a new one solves some of them, BUT no all; downloads are 'dangerous'\r
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
- transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);\r
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
transaction.commit();\r
\r
- } else { // small or medium screen device -> new Activity\r
+ } else {\r
Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
}\r
}\r
\r
+ private void startDownloadForPreview(OCFile file) {\r
+ if (mDualPane) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ transaction.commit();\r
+ mWaitingToPreview = file;\r
+ requestForDownload();\r
+ \r
+ } else {\r
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ startActivity(showDetailsIntent);\r
+ }\r
+ }\r
+\r
\r
+ private void startDetails(OCFile file) {\r
+ if (mDualPane && !file.isImage()) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
+ transaction.commit();\r
+ } else {\r
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ startActivity(showDetailsIntent);\r
+ }\r
+ }\r
+\r
+\r
/**\r
* {@inheritDoc}\r
*/\r
if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {\r
Log.d(TAG, "Download service connected");\r
mDownloaderBinder = (FileDownloaderBinder) service;\r
+ if (mWaitingToPreview != null) {\r
+ requestForDownload();\r
+ }\r
+ \r
} else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {\r
Log.d(TAG, "Upload service connected");\r
mUploaderBinder = (FileUploaderBinder) service;\r
if (mFileList != null)\r
mFileList.listDirectory();\r
if (mDualPane) {\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fragment != null)\r
- fragment.updateFileDetails(false);\r
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (fragment != null && fragment instanceof FileDetailFragment) {\r
+ FileDetailFragment detailFragment = (FileDetailFragment)fragment;\r
+ detailFragment.listenForTransferProgress();\r
+ detailFragment.updateFileDetails(false, false);\r
+ }\r
}\r
}\r
\r
msg.show();\r
OCFile removedFile = operation.getFile();\r
if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && removedFile.equals(details.getDisplayedFile()) ) {\r
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && removedFile.equals(details.getFile())) {\r
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment\r
transaction.commit();\r
OCFile renamedFile = operation.getFile();\r
if (result.isSuccess()) {\r
if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && renamedFile.equals(details.getDisplayedFile()) ) {\r
- details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) {\r
+ ((FileDetailFragment) details).updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));\r
}\r
}\r
if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {\r
fileListFragment.listDirectory();\r
}*/\r
if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && file.equals(details.getDisplayedFile()) ) {\r
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) {\r
if (downloading || uploading) {\r
- details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ ((FileDetailFragment)details).updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));\r
} else {\r
- details.updateFileDetails(downloading || uploading);\r
+ ((FileDetailFragment)details).updateFileDetails(false, true);\r
}\r
}\r
}\r
\r
\r
@Override\r
+ public void showFragmentWithDetails(OCFile file) {\r
+ if (mDualPane) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); \r
+ transaction.commit();\r
+ \r
+ } else {\r
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);\r
+ startActivity(showDetailsIntent);\r
+ }\r
+ }\r
+\r
public void onDismiss(EditNameDialog dialog) {\r
//dialog.dismiss();\r
if (dialog.getResult()) {\r
}\r
}\r
\r
+ private void requestForDownload() {\r
+ Account account = AccountUtils.getCurrentOwnCloudAccount(this);\r
+ if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {\r
+ Intent i = new Intent(this, FileDownloader.class);\r
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);\r
+ i.putExtra(FileDownloader.EXTRA_FILE, mWaitingToPreview);\r
+ startService(i);\r
+ }\r
+ }\r
+\r
+ \r
}\r
+
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.R;
+import com.owncloud.android.db.DbHandler;
+import com.owncloud.android.files.InstantUploadBroadcastReceiver;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.utils.FileStorageUtils;
+
+/**
+ * This Activity is used to display a list with images they could not be
+ * uploaded instantly. The images can be selected for delete or for a try again
+ * upload
+ *
+ * The entry-point for this activity is the 'Failed upload Notification" and a
+ * sub-menu underneath the 'Upload' menu-item
+ *
+ * @author andomaex / Matthias Baumann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more de/
+ */
+public class InstantUploadActivity extends Activity {
+
+ private static final String LOG_TAG = InstantUploadActivity.class.getSimpleName();
+ private LinearLayout listView;
+ private static final String retry_chexbox_tag = "retry_chexbox_tag";
+ private static int MAX_LOAD_IMAGES = 5;
+ private int lastLoadImageIdx = 0;
+
+ private SparseArray<String> fileList = null;
+ CheckBox failed_upload_all_cb;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.failed_upload_files);
+
+ Button delete_all_btn = (Button) findViewById(R.id.failed_upload_delete_all_btn);
+ delete_all_btn.setOnClickListener(getDeleteListner());
+ Button retry_all_btn = (Button) findViewById(R.id.failed_upload_retry_all_btn);
+ retry_all_btn.setOnClickListener(getRetryListner());
+ this.failed_upload_all_cb = (CheckBox) findViewById(R.id.failed_upload_headline_cb);
+ failed_upload_all_cb.setOnCheckedChangeListener(getCheckAllListener());
+ listView = (LinearLayout) findViewById(R.id.failed_upload_scrollviewlayout);
+
+ loadListView(true);
+
+ }
+
+ /**
+ * init the listview with ImageButtons, checkboxes and filename for every
+ * Image that was not successfully uploaded
+ *
+ * this method is call at Activity creation and on delete one ore more
+ * list-entry an on retry the upload by clicking the ImageButton or by click
+ * to the 'retry all' button
+ *
+ */
+ private void loadListView(boolean reset) {
+ DbHandler db = new DbHandler(getApplicationContext());
+ Cursor c = db.getFailedFiles();
+
+ if (reset) {
+ fileList = new SparseArray<String>();
+ listView.removeAllViews();
+ lastLoadImageIdx = 0;
+ }
+ if (c != null) {
+ try {
+ c.moveToPosition(lastLoadImageIdx);
+
+ while (c.moveToNext()) {
+
+ lastLoadImageIdx++;
+ String imp_path = c.getString(1);
+ String message = c.getString(4);
+ fileList.put(lastLoadImageIdx, imp_path);
+ LinearLayout rowLayout = getHorizontalLinearLayout(lastLoadImageIdx);
+ rowLayout.addView(getFileCheckbox(lastLoadImageIdx));
+ rowLayout.addView(getImageButton(imp_path, lastLoadImageIdx));
+ rowLayout.addView(getFileButton(imp_path, message, lastLoadImageIdx));
+ listView.addView(rowLayout);
+ Log.d(LOG_TAG, imp_path + " on idx: " + lastLoadImageIdx);
+ if (lastLoadImageIdx % MAX_LOAD_IMAGES == 0) {
+ break;
+ }
+ }
+ if (lastLoadImageIdx > 0) {
+ addLoadMoreButton(listView);
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+
+ private void addLoadMoreButton(LinearLayout listView) {
+ if (listView != null) {
+ Button loadmoreBtn = null;
+ View oldButton = listView.findViewById(42);
+ if (oldButton != null) {
+ // remove existing button
+ listView.removeView(oldButton);
+ // to add the button at the end
+ loadmoreBtn = (Button) oldButton;
+ } else {
+ // create a new button to add to the scoll view
+ loadmoreBtn = new Button(this);
+ loadmoreBtn.setId(42);
+ loadmoreBtn.setText(getString(R.string.failed_upload_load_more_images));
+ loadmoreBtn.setBackgroundResource(R.color.owncloud_white);
+ loadmoreBtn.setTextSize(12);
+ loadmoreBtn.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadListView(false);
+ }
+
+ });
+ }
+ listView.addView(loadmoreBtn);
+ }
+ }
+
+ /**
+ * provide a list of CheckBox instances, looked up from parent listview this
+ * list ist used to select/deselect all checkboxes at the list
+ *
+ * @return List<CheckBox>
+ */
+ private List<CheckBox> getCheckboxList() {
+ List<CheckBox> list = new ArrayList<CheckBox>();
+ for (int i = 0; i < listView.getChildCount(); i++) {
+ Log.d(LOG_TAG, "ListView has Childs: " + listView.getChildCount());
+ View childView = listView.getChildAt(i);
+ if (childView != null && childView instanceof ViewGroup) {
+ View checkboxView = getChildViews((ViewGroup) childView);
+ if (checkboxView != null && checkboxView instanceof CheckBox) {
+ Log.d(LOG_TAG, "found Child: " + checkboxView.getId() + " " + checkboxView.getClass());
+ list.add((CheckBox) checkboxView);
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * recursive called method, used from getCheckboxList method
+ *
+ * @param View
+ * @return View
+ */
+ private View getChildViews(ViewGroup view) {
+ if (view != null) {
+ for (int i = 0; i < view.getChildCount(); i++) {
+ View cb = view.getChildAt(i);
+ if (cb != null && cb instanceof ViewGroup) {
+ return getChildViews((ViewGroup) cb);
+ } else if (cb instanceof CheckBox) {
+ return cb;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * create a new OnCheckedChangeListener for the 'check all' checkbox *
+ *
+ * @return OnCheckedChangeListener to select all checkboxes at the list
+ */
+ private OnCheckedChangeListener getCheckAllListener() {
+ return new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ List<CheckBox> list = getCheckboxList();
+ for (CheckBox checkbox : list) {
+ ((CheckBox) checkbox).setChecked(isChecked);
+ }
+ }
+
+ };
+ }
+
+ /**
+ * Button click Listener for the retry button at the headline
+ *
+ * @return a Listener to perform a retry for all selected images
+ */
+ private OnClickListener getRetryListner() {
+ return new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ try {
+
+ List<CheckBox> list = getCheckboxList();
+ for (CheckBox checkbox : list) {
+ boolean to_retry = checkbox.isChecked();
+
+ Log.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_retry);
+ String img_path = fileList.get(checkbox.getId());
+ if (to_retry) {
+
+ final String msg = "Image-Path " + checkbox.getId() + " was checked: " + img_path;
+ Log.d(LOG_TAG, msg);
+ startUpload(img_path);
+ }
+
+ }
+ } finally {
+ // refresh the List
+ listView.removeAllViews();
+ loadListView(true);
+ if (failed_upload_all_cb != null) {
+ failed_upload_all_cb.setChecked(false);
+ }
+ }
+
+ }
+ };
+ }
+
+ /**
+ * Button click Listener for the delete button at the headline
+ *
+ * @return a Listener to perform a delete for all selected images
+ */
+ private OnClickListener getDeleteListner() {
+
+ return new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ final DbHandler dbh = new DbHandler(getApplicationContext());
+ try {
+ List<CheckBox> list = getCheckboxList();
+ for (CheckBox checkbox : list) {
+ boolean to_be_delete = checkbox.isChecked();
+
+ Log.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_be_delete);
+ String img_path = fileList.get(checkbox.getId());
+ Log.d(LOG_TAG, "Image-Path " + checkbox.getId() + " was checked: " + img_path);
+ if (to_be_delete) {
+ boolean deleted = dbh.removeIUPendingFile(img_path);
+ Log.d(LOG_TAG, "removing " + checkbox.getId() + " was : " + deleted);
+
+ }
+
+ }
+ } finally {
+ dbh.close();
+ // refresh the List
+ listView.removeAllViews();
+ loadListView(true);
+ if (failed_upload_all_cb != null) {
+ failed_upload_all_cb.setChecked(false);
+ }
+ }
+
+ }
+ };
+ }
+
+ private LinearLayout getHorizontalLinearLayout(int id) {
+ LinearLayout linearLayout = new LinearLayout(getApplicationContext());
+ linearLayout.setId(id);
+ linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ linearLayout.setGravity(Gravity.RIGHT);
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ return linearLayout;
+ }
+
+ private LinearLayout getVerticalLinearLayout() {
+ LinearLayout linearLayout = new LinearLayout(getApplicationContext());
+ linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ linearLayout.setGravity(Gravity.TOP);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ return linearLayout;
+ }
+
+ private View getFileButton(final String img_path, String message, int id) {
+
+ TextView failureTextView = new TextView(this);
+ failureTextView.setText(getString(R.string.failed_upload_failure_text) + message);
+ failureTextView.setBackgroundResource(R.color.owncloud_white);
+ failureTextView.setTextSize(8);
+ failureTextView.setOnLongClickListener(getOnLongClickListener(message));
+ failureTextView.setPadding(5, 5, 5, 10);
+ TextView retryButton = new TextView(this);
+ retryButton.setId(id);
+ retryButton.setText(img_path);
+ retryButton.setBackgroundResource(R.color.owncloud_white);
+ retryButton.setTextSize(8);
+ retryButton.setOnClickListener(getImageButtonOnClickListener(img_path));
+ retryButton.setOnLongClickListener(getOnLongClickListener(message));
+ retryButton.setPadding(5, 5, 5, 10);
+ LinearLayout verticalLayout = getVerticalLinearLayout();
+ verticalLayout.addView(retryButton);
+ verticalLayout.addView(failureTextView);
+
+ return verticalLayout;
+ }
+
+ private OnLongClickListener getOnLongClickListener(final String message) {
+ return new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ Log.d(LOG_TAG, message);
+ Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text)
+ + message, Toast.LENGTH_LONG);
+ toast.show();
+ return true;
+ }
+
+ };
+ }
+
+ private CheckBox getFileCheckbox(int id) {
+ CheckBox retryCB = new CheckBox(this);
+ retryCB.setId(id);
+ retryCB.setBackgroundResource(R.color.owncloud_white);
+ retryCB.setTextSize(8);
+ retryCB.setTag(retry_chexbox_tag);
+ return retryCB;
+ }
+
+ private ImageButton getImageButton(String img_path, int id) {
+ ImageButton imageButton = new ImageButton(this);
+ imageButton.setId(id);
+ imageButton.setClickable(true);
+ imageButton.setOnClickListener(getImageButtonOnClickListener(img_path));
+
+ // scale and add a thumbnail to the imagebutton
+ int base_scale_size = 32;
+ if (img_path != null) {
+ Log.d(LOG_TAG, "add " + img_path + " to Image Button");
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ Bitmap bitmap = BitmapFactory.decodeFile(img_path, options);
+ int width_tpm = options.outWidth, height_tmp = options.outHeight;
+ int scale = 3;
+ while (true) {
+ if (width_tpm / 2 < base_scale_size || height_tmp / 2 < base_scale_size) {
+ break;
+ }
+ width_tpm /= 2;
+ height_tmp /= 2;
+ scale++;
+ }
+
+ Log.d(LOG_TAG, "scale Imgae with: " + scale);
+ BitmapFactory.Options options2 = new BitmapFactory.Options();
+ options2.inSampleSize = scale;
+ bitmap = BitmapFactory.decodeFile(img_path, options2);
+
+ if (bitmap != null) {
+ Log.d(LOG_TAG, "loaded Bitmap Bytes: " + bitmap.getRowBytes());
+ imageButton.setImageBitmap(bitmap);
+ } else {
+ Log.d(LOG_TAG, "could not load imgage: " + img_path);
+ }
+ }
+ return imageButton;
+ }
+
+ private OnClickListener getImageButtonOnClickListener(final String img_path) {
+ return new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ startUpload(img_path);
+ loadListView(true);
+ }
+
+ };
+ }
+
+ /**
+ * start uploading a file to the INSTANT_UPLOD_DIR
+ *
+ * @param img_path
+ */
+ private void startUpload(String img_path) {
+ // extract filename
+ String filename = FileStorageUtils.getInstantUploadFilePath(img_path);
+ if (canInstantUpload()) {
+ Account account = AccountUtils.getCurrentOwnCloudAccount(InstantUploadActivity.this);
+ // add file again to upload queue
+ DbHandler db = new DbHandler(InstantUploadActivity.this);
+ try {
+ db.updateFileState(img_path, DbHandler.UPLOAD_STATUS_UPLOAD_LATER, null);
+ } finally {
+ db.close();
+ }
+
+ Intent i = new Intent(InstantUploadActivity.this, FileUploader.class);
+ i.putExtra(FileUploader.KEY_ACCOUNT, account);
+ i.putExtra(FileUploader.KEY_LOCAL_FILE, img_path);
+ i.putExtra(FileUploader.KEY_REMOTE_FILE, filename);
+ i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+ i.putExtra(com.owncloud.android.files.services.FileUploader.KEY_INSTANT_UPLOAD, true);
+
+ final String msg = "try to upload file with name :" + filename;
+ Log.d(LOG_TAG, msg);
+ Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text)
+ + filename, Toast.LENGTH_LONG);
+ toast.show();
+
+ startService(i);
+ } else {
+ Toast toast = Toast.makeText(InstantUploadActivity.this,
+ getString(R.string.failed_upload_retry_do_nothing_text) + filename, Toast.LENGTH_LONG);
+ toast.show();
+ }
+ }
+
+ private boolean canInstantUpload() {
+
+ if (!InstantUploadBroadcastReceiver.isOnline(this)
+ || (InstantUploadBroadcastReceiver.instantUploadViaWiFiOnly(this) && !InstantUploadBroadcastReceiver
+ .isConnectedViaWiFi(this))) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+}
\ No newline at end of file
String[] args = {getString(R.string.app_name)};
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1, R.string.common_no);
dialog.setOnConfirmationListener(UploadFilesActivity.this);
- mCurrentDialog = dialog;
- mCurrentDialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
+ dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
}
}
}
setResult(RESULT_OK_AND_MOVE, data);
finish();
}
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
}
@Override
public void onNeutral(String callerTag) {
Log.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag);
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
}
public void onCancel(String callerTag) {
/// nothing to do; don't finish, let the user change the selection
Log.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag);
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
}
dialog.setIndeterminate(true);
/// set message
- int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.text_placeholder);
+ int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence);
dialog.setMessage(getString(messageId));
/// set cancellation behavior
public final static String ARG_NEUTRAL_BTN_RES = "neutral_btn_res";
public final static String ARG_NEGATIVE_BTN_RES = "negative_btn_res";
+ public static final String FTAG_CONFIRMATION = "CONFIRMATION_FRAGMENT";
+
private ConfirmationDialogFragmentListener mListener;
/**
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mListener.onConfirmation(getTag());
+ dialog.dismiss();
}
});
if (neuBtn != -1)
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mListener.onNeutral(getTag());
+ dialog.dismiss();
}
});
if (negBtn != -1)
@Override
public void onClick(DialogInterface dialog, int which) {
mListener.onCancel(getTag());
+ dialog.dismiss();
}
});
return builder.create();
package com.owncloud.android.ui.fragment;\r
\r
import java.io.File;\r
+import java.lang.ref.WeakReference;\r
import java.util.ArrayList;\r
import java.util.List;\r
\r
\r
import android.accounts.Account;\r
import android.accounts.AccountManager;\r
-import android.annotation.SuppressLint;\r
import android.app.Activity;\r
import android.content.ActivityNotFoundException;\r
import android.content.BroadcastReceiver;\r
import android.content.Context;\r
import android.content.Intent;\r
import android.content.IntentFilter;\r
-import android.graphics.Bitmap;\r
-import android.graphics.BitmapFactory;\r
-import android.graphics.BitmapFactory.Options;\r
-import android.graphics.Point;\r
import android.net.Uri;\r
-import android.os.AsyncTask;\r
import android.os.Bundle;\r
import android.os.Handler;\r
-import android.support.v4.app.DialogFragment;\r
import android.support.v4.app.FragmentTransaction;\r
import android.util.Log;\r
-import android.view.Display;\r
import android.view.LayoutInflater;\r
import android.view.View;\r
import android.view.View.OnClickListener;\r
import android.widget.Button;\r
import android.widget.CheckBox;\r
import android.widget.ImageView;\r
+import android.widget.ProgressBar;\r
import android.widget.TextView;\r
import android.widget.Toast;\r
\r
import com.owncloud.android.authenticator.AccountAuthenticator;\r
import com.owncloud.android.datamodel.FileDataStorageManager;\r
import com.owncloud.android.datamodel.OCFile;\r
-import com.owncloud.android.files.services.FileDownloader;\r
import com.owncloud.android.files.services.FileObserverService;\r
import com.owncloud.android.files.services.FileUploader;\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
import com.owncloud.android.ui.activity.ConflictsResolveActivity;\r
import com.owncloud.android.ui.activity.FileDetailActivity;\r
import com.owncloud.android.ui.activity.FileDisplayActivity;\r
-import com.owncloud.android.ui.activity.TransferServiceGetter;\r
import com.owncloud.android.ui.dialog.EditNameDialog;\r
import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;\r
import com.owncloud.android.utils.OwnCloudVersion;\r
\r
import com.owncloud.android.R;\r
+\r
+import eu.alefzero.webdav.OnDatatransferProgressListener;\r
import eu.alefzero.webdav.WebdavClient;\r
import eu.alefzero.webdav.WebdavUtils;\r
\r
* This Fragment is used to display the details about a file.\r
* \r
* @author Bartek Przybylski\r
- * \r
+ * @author David A. Velasco\r
*/\r
public class FileDetailFragment extends SherlockFragment implements\r
- OnClickListener, ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener {\r
+ OnClickListener, \r
+ ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener,\r
+ FileFragment {\r
\r
public static final String EXTRA_FILE = "FILE";\r
public static final String EXTRA_ACCOUNT = "ACCOUNT";\r
\r
- private FileDetailFragment.ContainerActivity mContainerActivity;\r
+ private FileFragment.ContainerActivity mContainerActivity;\r
\r
private int mLayout;\r
private View mView;\r
private OCFile mFile;\r
private Account mAccount;\r
private FileDataStorageManager mStorageManager;\r
- private ImageView mPreview;\r
\r
- private DownloadFinishReceiver mDownloadFinishReceiver;\r
private UploadFinishReceiver mUploadFinishReceiver;\r
+ public ProgressListener mProgressListener;\r
\r
private Handler mHandler;\r
private RemoteOperation mLastRemoteOperation;\r
- private DialogFragment mCurrentDialog;\r
-\r
+ \r
private static final String TAG = FileDetailFragment.class.getSimpleName();\r
public static final String FTAG = "FileDetails"; \r
public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";\r
mAccount = null;\r
mStorageManager = null;\r
mLayout = R.layout.file_details_empty;\r
+ mProgressListener = null;\r
}\r
\r
\r
mAccount = ocAccount;\r
mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment \r
mLayout = R.layout.file_details_empty;\r
+ mProgressListener = null;\r
}\r
\r
\r
mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this);\r
mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this);\r
//mView.findViewById(R.id.fdShareBtn).setOnClickListener(this);\r
- mPreview = (ImageView)mView.findViewById(R.id.fdPreview);\r
+ ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar);\r
+ mProgressListener = new ProgressListener(progressBar);\r
}\r
\r
- updateFileDetails(false);\r
+ updateFileDetails(false, false);\r
return view;\r
}\r
\r
super.onAttach(activity);\r
try {\r
mContainerActivity = (ContainerActivity) activity;\r
+ \r
} catch (ClassCastException e) {\r
throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName());\r
}\r
public void onActivityCreated(Bundle savedInstanceState) {\r
super.onActivityCreated(savedInstanceState);\r
if (mAccount != null) {\r
- mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());;\r
+ mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());\r
}\r
}\r
\r
\r
@Override\r
public void onSaveInstanceState(Bundle outState) {\r
- Log.i(getClass().toString(), "onSaveInstanceState() start");\r
super.onSaveInstanceState(outState);\r
outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile);\r
outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount);\r
- Log.i(getClass().toString(), "onSaveInstanceState() end");\r
}\r
\r
+ @Override\r
+ public void onStart() {\r
+ super.onStart();\r
+ listenForTransferProgress();\r
+ }\r
\r
@Override\r
public void onResume() {\r
super.onResume();\r
- \r
- mDownloadFinishReceiver = new DownloadFinishReceiver();\r
- IntentFilter filter = new IntentFilter(\r
- FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
- getActivity().registerReceiver(mDownloadFinishReceiver, filter);\r
- \r
mUploadFinishReceiver = new UploadFinishReceiver();\r
- filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);\r
+ IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);\r
getActivity().registerReceiver(mUploadFinishReceiver, filter);\r
- \r
- mPreview = (ImageView)mView.findViewById(R.id.fdPreview);\r
+\r
}\r
\r
+\r
@Override\r
public void onPause() {\r
super.onPause();\r
- \r
- getActivity().unregisterReceiver(mDownloadFinishReceiver);\r
- mDownloadFinishReceiver = null;\r
- \r
- getActivity().unregisterReceiver(mUploadFinishReceiver);\r
- mUploadFinishReceiver = null;\r
- \r
- if (mPreview != null) {\r
- mPreview = null;\r
+ if (mUploadFinishReceiver != null) {\r
+ getActivity().unregisterReceiver(mUploadFinishReceiver);\r
+ mUploadFinishReceiver = null;\r
}\r
}\r
\r
+ \r
+ @Override\r
+ public void onStop() {\r
+ super.onStop();\r
+ leaveTransferProgress();\r
+ }\r
+\r
+ \r
@Override\r
public View getView() {\r
return super.getView() == null ? mView : super.getView();\r
}\r
\r
\r
- \r
@Override\r
public void onClick(View v) {\r
switch (v.getId()) {\r
// update ui \r
boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference\r
\r
}\r
break;\r
FileObserverService.CMD_DEL_OBSERVED_FILE));\r
intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile);\r
intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);\r
- Log.e(TAG, "starting observer service");\r
getActivity().startService(intent);\r
\r
if (mFile.keepInSync()) {\r
mFile.isDown() ? R.string.confirmation_remove_local : -1,\r
R.string.common_cancel);\r
confDialog.setOnConfirmationListener(this);\r
- mCurrentDialog = confDialog;\r
- mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
+ confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
break;\r
}\r
case R.id.fdOpenBtn: {\r
- String storagePath = mFile.getStoragePath();\r
- String encodedStoragePath = WebdavUtils.encodePath(storagePath);\r
- try {\r
- Intent i = new Intent(Intent.ACTION_VIEW);\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());\r
- i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
- startActivity(i);\r
- \r
- } catch (Throwable t) {\r
- Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());\r
- boolean toastIt = true; \r
- String mimeType = "";\r
- try {\r
- Intent i = new Intent(Intent.ACTION_VIEW);\r
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));\r
- if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {\r
- if (mimeType != null) {\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);\r
- } else {\r
- // desperate try\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");\r
- }\r
- i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
- startActivity(i);\r
- toastIt = false;\r
- }\r
- \r
- } catch (IndexOutOfBoundsException e) {\r
- Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);\r
- \r
- } catch (ActivityNotFoundException e) {\r
- Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");\r
- \r
- } catch (Throwable th) {\r
- Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);\r
- \r
- } finally {\r
- if (toastIt) {\r
- Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();\r
- }\r
- }\r
- \r
- }\r
+ openFile();\r
break;\r
}\r
default:\r
}\r
\r
\r
+ /**\r
+ * Opens mFile.\r
+ */\r
+ private void openFile() {\r
+ \r
+ String storagePath = mFile.getStoragePath();\r
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);\r
+ try {\r
+ Intent i = new Intent(Intent.ACTION_VIEW);\r
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());\r
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
+ startActivity(i);\r
+ \r
+ } catch (Throwable t) {\r
+ Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());\r
+ boolean toastIt = true; \r
+ String mimeType = "";\r
+ try {\r
+ Intent i = new Intent(Intent.ACTION_VIEW);\r
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));\r
+ if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {\r
+ if (mimeType != null) {\r
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);\r
+ } else {\r
+ // desperate try\r
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");\r
+ }\r
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
+ startActivity(i);\r
+ toastIt = false;\r
+ }\r
+ \r
+ } catch (IndexOutOfBoundsException e) {\r
+ Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);\r
+ \r
+ } catch (ActivityNotFoundException e) {\r
+ Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");\r
+ \r
+ } catch (Throwable th) {\r
+ Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);\r
+ \r
+ } finally {\r
+ if (toastIt) {\r
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();\r
+ }\r
+ }\r
+ \r
+ }\r
+ }\r
+\r
+\r
@Override\r
public void onConfirmation(String callerTag) {\r
if (callerTag.equals(FTAG_CONFIRMATION)) {\r
getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
}\r
}\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
}\r
\r
@Override\r
mStorageManager.saveFile(mFile);\r
updateFileDetails(mFile, mAccount);\r
}\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
}\r
\r
@Override\r
public void onCancel(String callerTag) {\r
Log.d(TAG, "REMOVAL CANCELED");\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
}\r
\r
\r
\r
\r
/**\r
- * Can be used to get the file that is currently being displayed.\r
- * @return The file on the screen.\r
+ * {@inheritDoc}\r
*/\r
- public OCFile getDisplayedFile(){\r
+ public OCFile getFile(){\r
return mFile;\r
}\r
\r
mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());\r
}\r
mAccount = ocAccount;\r
- updateFileDetails(false);\r
+ updateFileDetails(false, false);\r
}\r
\r
\r
*\r
* TODO Remove parameter when the transferring state of files is kept in database. \r
* \r
+ * TODO REFACTORING! this method called 5 times before every time the fragment is shown! \r
+ * \r
* @param transferring Flag signaling if the file should be considered as downloading or uploading, \r
* although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and \r
* {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.\r
- * \r
+ * \r
+ * @param refresh If 'true', try to refresh the hold file from the database\r
*/\r
- public void updateFileDetails(boolean transferring) {\r
+ public void updateFileDetails(boolean transferring, boolean refresh) {\r
\r
- if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {\r
+ if (readyToShow()) {\r
+ \r
+ if (refresh && mStorageManager != null) {\r
+ mFile = mStorageManager.getFileByPath(mFile.getRemotePath());\r
+ }\r
\r
// set file details\r
setFilename(mFile.getFileName());\r
setButtonsForTransferring();\r
\r
} else if (mFile.isDown()) {\r
- // Update preview\r
- if (mFile.getMimetype().startsWith("image/")) {\r
- BitmapLoader bl = new BitmapLoader();\r
- bl.execute(new String[]{mFile.getStoragePath()});\r
- }\r
\r
setButtonsForDown();\r
\r
\r
\r
/**\r
+ * Checks if the fragment is ready to show details of a OCFile\r
+ * \r
+ * @return 'True' when the fragment is ready to show details of a file\r
+ */\r
+ private boolean readyToShow() {\r
+ return (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment); \r
+ }\r
+\r
+\r
+\r
+ /**\r
* Updates the filename in view\r
* @param filename to set\r
*/\r
((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false);\r
((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false);\r
getView().findViewById(R.id.fdKeepInSync).setEnabled(false);\r
+ \r
+ // show the progress bar for the transfer\r
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);\r
+ progressBar.setVisibility(View.VISIBLE);\r
+ TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);\r
+ progressText.setVisibility(View.VISIBLE);\r
+ FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();\r
+ FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();\r
+ if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {\r
+ progressText.setText(R.string.downloader_download_in_progress_ticker);\r
+ } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {\r
+ progressText.setText(R.string.uploader_upload_in_progress_ticker);\r
+ }\r
}\r
}\r
\r
+\r
/**\r
* Enables or disables buttons for a file locally available \r
*/\r
((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);\r
((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);\r
getView().findViewById(R.id.fdKeepInSync).setEnabled(true);\r
+ \r
+ // hides the progress bar\r
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);\r
+ progressBar.setVisibility(View.GONE);\r
+ TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);\r
+ progressText.setVisibility(View.GONE);\r
}\r
}\r
\r
((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);\r
((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);\r
getView().findViewById(R.id.fdKeepInSync).setEnabled(true);\r
+ \r
+ // hides the progress bar\r
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);\r
+ progressBar.setVisibility(View.GONE);\r
+ TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);\r
+ progressText.setVisibility(View.GONE);\r
}\r
}\r
\r
\r
\r
/**\r
- * Interface to implement by any Activity that includes some instance of FileDetailFragment\r
- * \r
- * @author David A. Velasco\r
- */\r
- public interface ContainerActivity extends TransferServiceGetter {\r
-\r
- /**\r
- * Callback method invoked when the detail fragment wants to notice its container \r
- * activity about a relevant state the file shown by the fragment.\r
- * \r
- * Added to notify to FileDisplayActivity about the need of refresh the files list. \r
- * \r
- * Currently called when:\r
- * - a download is started;\r
- * - a rename is completed;\r
- * - a deletion is completed;\r
- * - the 'inSync' flag is changed;\r
- */\r
- public void onFileStateChanged();\r
- \r
- }\r
- \r
-\r
- /**\r
- * Once the file download has finished -> update view\r
- * @author Bartek Przybylski\r
- */\r
- private class DownloadFinishReceiver extends BroadcastReceiver {\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
-\r
- if (!isEmpty() && accountName.equals(mAccount.name)) {\r
- boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false);\r
- String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
- if (mFile.getRemotePath().equals(downloadedRemotePath)) {\r
- if (downloadWasFine) {\r
- mFile = mStorageManager.getFileByPath(downloadedRemotePath);\r
- }\r
- updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
* Once the file upload has finished -> update view\r
* \r
* Being notified about the finish of an upload is necessary for the next sequence:\r
msg.show();\r
}\r
getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done\r
- updateFileDetails(false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server\r
+ updateFileDetails(false, false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server\r
}\r
}\r
}\r
}\r
\r
\r
- class BitmapLoader extends AsyncTask<String, Void, Bitmap> {\r
- @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20\r
- @Override\r
- protected Bitmap doInBackground(String... params) {\r
- Bitmap result = null;\r
- if (params.length != 1) return result;\r
- String storagePath = params[0];\r
- try {\r
-\r
- BitmapFactory.Options options = new Options();\r
- options.inScaled = true;\r
- options.inPurgeable = true;\r
- options.inJustDecodeBounds = true;\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {\r
- options.inPreferQualityOverSpeed = false;\r
- }\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {\r
- options.inMutable = false;\r
- }\r
-\r
- result = BitmapFactory.decodeFile(storagePath, options);\r
- options.inJustDecodeBounds = false;\r
-\r
- int width = options.outWidth;\r
- int height = options.outHeight;\r
- int scale = 1;\r
- if (width >= 2048 || height >= 2048) {\r
- scale = (int) Math.ceil((Math.ceil(Math.max(height, width) / 2048.)));\r
- options.inSampleSize = scale;\r
- }\r
- Display display = getActivity().getWindowManager().getDefaultDisplay();\r
- Point size = new Point();\r
- int screenwidth;\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) {\r
- display.getSize(size);\r
- screenwidth = size.x;\r
- } else {\r
- screenwidth = display.getWidth();\r
- }\r
-\r
- Log.e("ASD", "W " + width + " SW " + screenwidth);\r
-\r
- if (width > screenwidth) {\r
- scale = (int) Math.ceil((float)width / screenwidth);\r
- options.inSampleSize = scale;\r
- }\r
-\r
- result = BitmapFactory.decodeFile(storagePath, options);\r
-\r
- Log.e("ASD", "W " + options.outWidth + " SW " + options.outHeight);\r
-\r
- } catch (OutOfMemoryError e) {\r
- result = null;\r
- Log.e(TAG, "Out of memory occured for file with size " + storagePath);\r
- \r
- } catch (NoSuchFieldError e) {\r
- result = null;\r
- Log.e(TAG, "Error from access to unexisting field despite protection " + storagePath);\r
- \r
- } catch (Throwable t) {\r
- result = null;\r
- Log.e(TAG, "Unexpected error while creating image preview " + storagePath, t);\r
- }\r
- return result;\r
- }\r
- @Override\r
- protected void onPostExecute(Bitmap result) {\r
- if (result != null && mPreview != null) {\r
- mPreview.setImageBitmap(result);\r
- }\r
- }\r
- \r
- }\r
-\r
/**\r
* {@inheritDoc}\r
*/\r
\r
} else {\r
if (operation.transferWasRequested()) {\r
+ setButtonsForTransferring();\r
mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so \r
// checking the service to see if the file is downloading results in FALSE\r
} else {\r
}\r
}\r
}\r
+ \r
+ \r
+ public void listenForTransferProgress() {\r
+ if (mProgressListener != null) {\r
+ if (mContainerActivity.getFileDownloaderBinder() != null) {\r
+ mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);\r
+ }\r
+ if (mContainerActivity.getFileUploaderBinder() != null) {\r
+ mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);\r
+ }\r
+ }\r
+ }\r
+ \r
+ \r
+ public void leaveTransferProgress() {\r
+ if (mProgressListener != null) {\r
+ if (mContainerActivity.getFileDownloaderBinder() != null) {\r
+ mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);\r
+ }\r
+ if (mContainerActivity.getFileUploaderBinder() != null) {\r
+ mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ \r
+ /**\r
+ * Helper class responsible for updating the progress bar shown for file uploading or downloading \r
+ * \r
+ * @author David A. Velasco\r
+ */\r
+ private class ProgressListener implements OnDatatransferProgressListener {\r
+ int mLastPercent = 0;\r
+ WeakReference<ProgressBar> mProgressBar = null;\r
+ \r
+ ProgressListener(ProgressBar progressBar) {\r
+ mProgressBar = new WeakReference<ProgressBar>(progressBar);\r
+ }\r
+ \r
+ @Override\r
+ public void onTransferProgress(long progressRate) {\r
+ // old method, nothing here\r
+ };\r
+\r
+ @Override\r
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {\r
+ int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));\r
+ if (percent != mLastPercent) {\r
+ ProgressBar pb = mProgressBar.get();\r
+ if (pb != null) {\r
+ pb.setProgress(percent);\r
+ pb.postInvalidate();\r
+ }\r
+ }\r
+ mLastPercent = percent;\r
+ }\r
+\r
+ };\r
+ \r
+\r
\r
}\r
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.content.Intent;
+import android.support.v4.app.Fragment;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.TransferServiceGetter;
+
+/**
+ * Common methods for {@link Fragment}s containing {@link OCFile}s
+ *
+ * @author David A. Velasco
+ *
+ */
+public interface FileFragment {
+
+ /**
+ * Getter for the hold {@link OCFile}
+ *
+ * @return The {@link OCFile} hold
+ */
+ public OCFile getFile();
+
+
+ /**
+ * Interface to implement by any Activity that includes some instance of FileFragment
+ *
+ * @author David A. Velasco
+ */
+ public interface ContainerActivity extends TransferServiceGetter {
+
+ /**
+ * Callback method invoked when the detail fragment wants to notice its container
+ * activity about a relevant state the file shown by the fragment.
+ *
+ * Added to notify to FileDisplayActivity about the need of refresh the files list.
+ *
+ * Currently called when:
+ * - a download is started;
+ * - a rename is completed;
+ * - a deletion is completed;
+ * - the 'inSync' flag is changed;
+ */
+ public void onFileStateChanged();
+
+ /**
+ * Request the parent activity to show the details of an {@link OCFile}.
+ *
+ * @param file File to show details
+ */
+ public void showFragmentWithDetails(OCFile file);
+
+
+ }
+
+}
private Handler mHandler;
private OCFile mTargetFile;
- private DialogFragment mCurrentDialog;
-
/**
* {@inheritDoc}
*/
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
- inflater.inflate(R.menu.file_context_menu, menu);
+ inflater.inflate(R.menu.file_actions_menu, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
List<Integer> toHide = new ArrayList<Integer>();
MenuItem item = null;
if (targetFile.isDirectory()) {
// contextual menu for folders
- toHide.add(R.id.open_file_item);
- toHide.add(R.id.download_file_item);
- toHide.add(R.id.cancel_download_item);
- toHide.add(R.id.cancel_upload_item);
+ toHide.add(R.id.action_open_file_with);
+ toHide.add(R.id.action_download_file);
+ toHide.add(R.id.action_cancel_download);
+ toHide.add(R.id.action_cancel_upload);
+ toHide.add(R.id.action_see_details);
if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
- toDisable.add(R.id.rename_file_item);
- toDisable.add(R.id.remove_file_item);
+ toDisable.add(R.id.action_rename_file);
+ toDisable.add(R.id.action_remove_file);
}
} else {
// contextual menu for regular files
if (targetFile.isDown()) {
- toHide.add(R.id.cancel_download_item);
- toHide.add(R.id.cancel_upload_item);
- item = menu.findItem(R.id.download_file_item);
+ toHide.add(R.id.action_cancel_download);
+ toHide.add(R.id.action_cancel_upload);
+ item = menu.findItem(R.id.action_download_file);
if (item != null) {
item.setTitle(R.string.filedetails_sync_file);
}
} else {
- toHide.add(R.id.open_file_item);
+ toHide.add(R.id.action_open_file_with);
}
if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
- toHide.add(R.id.download_file_item);
- toHide.add(R.id.cancel_upload_item);
- toDisable.add(R.id.open_file_item);
- toDisable.add(R.id.rename_file_item);
- toDisable.add(R.id.remove_file_item);
+ toHide.add(R.id.action_download_file);
+ toHide.add(R.id.action_cancel_upload);
+ toDisable.add(R.id.action_open_file_with);
+ toDisable.add(R.id.action_rename_file);
+ toDisable.add(R.id.action_remove_file);
} else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
- toHide.add(R.id.download_file_item);
- toHide.add(R.id.cancel_download_item);
- toDisable.add(R.id.open_file_item);
- toDisable.add(R.id.rename_file_item);
- toDisable.add(R.id.remove_file_item);
+ toHide.add(R.id.action_download_file);
+ toHide.add(R.id.action_cancel_download);
+ toDisable.add(R.id.action_open_file_with);
+ toDisable.add(R.id.action_rename_file);
+ toDisable.add(R.id.action_remove_file);
} else {
- toHide.add(R.id.cancel_download_item);
- toHide.add(R.id.cancel_upload_item);
+ toHide.add(R.id.action_cancel_download);
+ toHide.add(R.id.action_cancel_upload);
}
}
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
mTargetFile = (OCFile) mAdapter.getItem(info.position);
switch (item.getItemId()) {
- case R.id.rename_file_item: {
+ case R.id.action_rename_file: {
EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
dialog.show(getFragmentManager(), EditNameDialog.TAG);
return true;
}
- case R.id.remove_file_item: {
+ case R.id.action_remove_file: {
int messageStringId = R.string.confirmation_remove_alert;
int posBtnStringId = R.string.confirmation_remove_remote;
int neuBtnStringId = -1;
neuBtnStringId,
R.string.common_cancel);
confDialog.setOnConfirmationListener(this);
- mCurrentDialog = confDialog;
- mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
+ confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
return true;
}
- case R.id.open_file_item: {
+ case R.id.action_open_file_with: {
String storagePath = mTargetFile.getStoragePath();
String encodedStoragePath = WebdavUtils.encodePath(storagePath);
try {
}
return true;
}
- case R.id.download_file_item: {
+ case R.id.action_download_file: {
Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
return true;
}
- case R.id.cancel_download_item: {
+ case R.id.action_cancel_download: {
FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
}
return true;
}
- case R.id.cancel_upload_item: {
+ case R.id.action_cancel_upload: {
FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
}
return true;
}
+ case R.id.action_see_details: {
+ ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mTargetFile);
+ return true;
+ }
default:
return super.onContextItemSelected(item);
}
getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
}
- if (mCurrentDialog != null) {
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
- }
}
}
mTargetFile.setStoragePath(null);
mContainerActivity.getStorageManager().saveFile(mTargetFile);
}
- if (mCurrentDialog != null) {
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
- }
listDirectory();
mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
}
@Override
public void onCancel(String callerTag) {
Log.d(TAG, "REMOVAL CANCELED");
- if (mCurrentDialog != null) {
- mCurrentDialog.dismiss();
- mCurrentDialog = null;
- }
}
--- /dev/null
+/* ownCloud Android client application
+ *
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.preview;
+
+import java.lang.ref.WeakReference;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragment;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.ui.fragment.FileFragment;
+
+import com.owncloud.android.R;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
+
+/**
+ * This Fragment is used to monitor the progress of a file downloading.
+ *
+ * @author David A. Velasco
+ */
+public class FileDownloadFragment extends SherlockFragment implements OnClickListener, FileFragment {
+
+ public static final String EXTRA_FILE = "FILE";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+ private static final String EXTRA_ERROR = "ERROR";
+
+ private FileFragment.ContainerActivity mContainerActivity;
+
+ private View mView;
+ private OCFile mFile;
+ private Account mAccount;
+ private FileDataStorageManager mStorageManager;
+
+ public ProgressListener mProgressListener;
+ private boolean mListening;
+
+ private static final String TAG = FileDownloadFragment.class.getSimpleName();
+
+ private boolean mIgnoreFirstSavedState;
+ private boolean mError;
+
+
+ /**
+ * Creates an empty details fragment.
+ *
+ * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically.
+ */
+ public FileDownloadFragment() {
+ mFile = null;
+ mAccount = null;
+ mStorageManager = null;
+ mProgressListener = null;
+ mListening = false;
+ mIgnoreFirstSavedState = false;
+ mError = false;
+ }
+
+
+ /**
+ * Creates a details fragment.
+ *
+ * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
+ *
+ * @param fileToDetail An {@link OCFile} to show in the fragment
+ * @param ocAccount An ownCloud account; needed to start downloads
+ * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution
+ */
+ public FileDownloadFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) {
+ mFile = fileToDetail;
+ mAccount = ocAccount;
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
+ mProgressListener = null;
+ mListening = false;
+ mIgnoreFirstSavedState = ignoreFirstSavedState;
+ mError = false;
+ }
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ if (savedInstanceState != null) {
+ if (!mIgnoreFirstSavedState) {
+ mFile = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_ACCOUNT);
+ mError = savedInstanceState.getBoolean(FileDownloadFragment.EXTRA_ERROR);
+ } else {
+ mIgnoreFirstSavedState = false;
+ }
+ }
+
+ View view = null;
+ view = inflater.inflate(R.layout.file_download_fragment, container, false);
+ mView = view;
+
+ ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.progressBar);
+ mProgressListener = new ProgressListener(progressBar);
+
+ ((Button)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this);
+
+ if (mError) {
+ setButtonsForRemote();
+ } else {
+ setButtonsForTransferring();
+ }
+
+ return view;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mContainerActivity = (ContainerActivity) activity;
+
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName());
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (mAccount != null) {
+ mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());;
+ }
+ }
+
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(FileDownloadFragment.EXTRA_FILE, mFile);
+ outState.putParcelable(FileDownloadFragment.EXTRA_ACCOUNT, mAccount);
+ outState.putBoolean(FileDownloadFragment.EXTRA_ERROR, mError);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ listenForTransferProgress();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ leaveTransferProgress();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+
+ @Override
+ public View getView() {
+ if (!mListening) {
+ listenForTransferProgress();
+ }
+ return super.getView() == null ? mView : super.getView();
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.cancelBtn: {
+ FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
+ if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
+ downloaderBinder.cancel(mAccount, mFile);
+ getActivity().finish(); // :)
+ /*
+ leaveTransferProgress();
+ if (mFile.isDown()) {
+ setButtonsForDown();
+ } else {
+ setButtonsForRemote();
+ }
+ */
+ }
+ break;
+ }
+ default:
+ Log.e(TAG, "Incorrect view clicked!");
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+
+ /**
+ * Updates the view depending upon the state of the downloading file.
+ *
+ * @param transferring When true, the view must be updated assuming that the holded file is
+ * downloading, no matter what the downloaderBinder says.
+ */
+ public void updateView(boolean transferring) {
+ // configure UI for depending upon local state of the file
+ FileDownloaderBinder downloaderBinder = (mContainerActivity == null) ? null : mContainerActivity.getFileDownloaderBinder();
+ if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile))) {
+ setButtonsForTransferring();
+
+ } else if (mFile.isDown()) {
+
+ setButtonsForDown();
+
+ } else {
+ setButtonsForRemote();
+ }
+ getView().invalidate();
+
+ }
+
+
+ /**
+ * Enables or disables buttons for a file being downloaded
+ */
+ private void setButtonsForTransferring() {
+ getView().findViewById(R.id.cancelBtn).setVisibility(View.VISIBLE);
+
+ // show the progress bar for the transfer
+ getView().findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
+ TextView progressText = (TextView)getView().findViewById(R.id.progressText);
+ progressText.setText(R.string.downloader_download_in_progress_ticker);
+ progressText.setVisibility(View.VISIBLE);
+
+ // hides the error icon
+ getView().findViewById(R.id.errorText).setVisibility(View.GONE);
+ getView().findViewById(R.id.error_image).setVisibility(View.GONE);
+ }
+
+
+ /**
+ * Enables or disables buttons for a file locally available
+ */
+ private void setButtonsForDown() {
+ getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE);
+
+ // hides the progress bar
+ getView().findViewById(R.id.progressBar).setVisibility(View.GONE);
+
+ // updates the text message
+ TextView progressText = (TextView)getView().findViewById(R.id.progressText);
+ progressText.setText(R.string.common_loading);
+ progressText.setVisibility(View.VISIBLE);
+
+ // hides the error icon
+ getView().findViewById(R.id.errorText).setVisibility(View.GONE);
+ getView().findViewById(R.id.error_image).setVisibility(View.GONE);
+ }
+
+
+ /**
+ * Enables or disables buttons for a file not locally available
+ *
+ * Currently, this is only used when a download was failed
+ */
+ private void setButtonsForRemote() {
+ getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE);
+
+ // hides the progress bar and message
+ getView().findViewById(R.id.progressBar).setVisibility(View.GONE);
+ getView().findViewById(R.id.progressText).setVisibility(View.GONE);
+
+ // shows the error icon and message
+ getView().findViewById(R.id.errorText).setVisibility(View.VISIBLE);
+ getView().findViewById(R.id.error_image).setVisibility(View.VISIBLE);
+ }
+
+
+ public void listenForTransferProgress() {
+ if (mProgressListener != null && !mListening) {
+ if (mContainerActivity.getFileDownloaderBinder() != null) {
+ mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ mListening = true;
+ setButtonsForTransferring();
+ }
+ }
+ }
+
+
+ public void leaveTransferProgress() {
+ if (mProgressListener != null) {
+ if (mContainerActivity.getFileDownloaderBinder() != null) {
+ mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ mListening = false;
+ }
+ }
+ }
+
+
+ /**
+ * Helper class responsible for updating the progress bar shown for file uploading or downloading
+ *
+ * @author David A. Velasco
+ */
+ private class ProgressListener implements OnDatatransferProgressListener {
+ int mLastPercent = 0;
+ WeakReference<ProgressBar> mProgressBar = null;
+
+ ProgressListener(ProgressBar progressBar) {
+ mProgressBar = new WeakReference<ProgressBar>(progressBar);
+ }
+
+ @Override
+ public void onTransferProgress(long progressRate) {
+ // old method, nothing here
+ };
+
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {
+ int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
+ if (percent != mLastPercent) {
+ ProgressBar pb = mProgressBar.get();
+ if (pb != null) {
+ pb.setProgress(percent);
+ pb.postInvalidate();
+ }
+ }
+ mLastPercent = percent;
+ }
+
+ }
+
+
+ public void setError(boolean error) {
+ mError = error;
+ };
+
+
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.preview;
+
+import org.apache.commons.httpclient.methods.PostMethod;
+
+import android.accounts.Account;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.Window;
+import com.owncloud.android.datamodel.DataStorageManager;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.ui.activity.FileDetailActivity;
+import com.owncloud.android.ui.fragment.FileDetailFragment;
+import com.owncloud.android.ui.fragment.FileFragment;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.R;
+
+/**
+ * Used as an utility to preview image files contained in an ownCloud account.
+ *
+ * @author David A. Velasco
+ */
+public class PreviewImageActivity extends SherlockFragmentActivity implements FileFragment.ContainerActivity, ViewPager.OnPageChangeListener, OnTouchListener {
+
+ public static final int DIALOG_SHORT_WAIT = 0;
+
+ public static final String TAG = PreviewImageActivity.class.getSimpleName();
+
+ public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
+ private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER";
+
+ private OCFile mFile;
+ private OCFile mParentFolder;
+ private Account mAccount;
+ private DataStorageManager mStorageManager;
+
+ private ViewPager mViewPager;
+ private PreviewImagePagerAdapter mPreviewImagePagerAdapter;
+
+ private FileDownloaderBinder mDownloaderBinder = null;
+ private ServiceConnection mDownloadConnection, mUploadConnection = null;
+ private FileUploaderBinder mUploaderBinder = null;
+
+ private boolean mRequestWaitingForBinder;
+
+ private DownloadFinishReceiver mDownloadFinishReceiver;
+
+ private boolean mFullScreen;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);
+ mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.isImage()) {
+ throw new IllegalArgumentException("Non-image file passed as argument");
+ }
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ setContentView(R.layout.preview_image_activity);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(mFile.getFileName());
+ actionBar.hide();
+
+ mFullScreen = true;
+
+ mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
+ mParentFolder = mStorageManager.getFileById(mFile.getParentId());
+ if (mParentFolder == null) {
+ // should not be necessary
+ mParentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
+ }
+
+ if (savedInstanceState != null) {
+ mRequestWaitingForBinder = savedInstanceState.getBoolean(KEY_WAITING_FOR_BINDER);
+ } else {
+ mRequestWaitingForBinder = false;
+ }
+
+ createViewPager();
+
+ }
+
+ private void createViewPager() {
+ mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), mParentFolder, mAccount, mStorageManager);
+ mViewPager = (ViewPager) findViewById(R.id.fragmentPager);
+ int position = mPreviewImagePagerAdapter.getFilePosition(mFile);
+ position = (position >= 0) ? position : 0;
+ mViewPager.setAdapter(mPreviewImagePagerAdapter);
+ mViewPager.setOnPageChangeListener(this);
+ mViewPager.setCurrentItem(position);
+ if (position == 0 && !mFile.isDown()) {
+ // this is necessary because mViewPager.setCurrentItem(0) just after setting the adapter does not result in a call to #onPageSelected(0)
+ mRequestWaitingForBinder = true;
+ }
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mDownloadConnection = new PreviewImageServiceConnection();
+ bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);
+ mUploadConnection = new PreviewImageServiceConnection();
+ bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_WAITING_FOR_BINDER, mRequestWaitingForBinder);
+ }
+
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private class PreviewImageServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+
+ if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) {
+ mDownloaderBinder = (FileDownloaderBinder) service;
+ if (mRequestWaitingForBinder) {
+ mRequestWaitingForBinder = false;
+ Log.d(TAG, "Simulating reselection of current page after connection of download binder");
+ onPageSelected(mViewPager.getCurrentItem());
+ }
+
+ } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) {
+ Log.d(TAG, "Upload service connected");
+ mUploaderBinder = (FileUploaderBinder) service;
+ } else {
+ return;
+ }
+
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) {
+ Log.d(TAG, "Download service suddenly disconnected");
+ mDownloaderBinder = null;
+ } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) {
+ Log.d(TAG, "Upload service suddenly disconnected");
+ mUploaderBinder = null;
+ }
+ }
+ };
+
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mDownloadConnection != null) {
+ unbindService(mDownloadConnection);
+ mDownloadConnection = null;
+ }
+ if (mUploadConnection != null) {
+ unbindService(mUploadConnection);
+ mUploadConnection = null;
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ boolean returnValue = false;
+
+ switch(item.getItemId()){
+ case android.R.id.home:
+ backToDisplayActivity();
+ returnValue = true;
+ break;
+ default:
+ returnValue = super.onOptionsItemSelected(item);
+ }
+
+ return returnValue;
+ }
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ //Log.e(TAG, "ACTIVITY, ONRESUME");
+ mDownloadFinishReceiver = new DownloadFinishReceiver();
+ IntentFilter filter = new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE);
+ registerReceiver(mDownloadFinishReceiver, filter);
+ }
+
+ @Override
+ protected void onPostResume() {
+ //Log.e(TAG, "ACTIVITY, ONPOSTRESUME");
+ super.onPostResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ unregisterReceiver(mDownloadFinishReceiver);
+ mDownloadFinishReceiver = null;
+ }
+
+
+ private void backToDisplayActivity() {
+ /*
+ Intent intent = new Intent(this, FileDisplayActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
+ intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ startActivity(intent);
+ */
+ finish();
+ }
+
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ Dialog dialog = null;
+ switch (id) {
+ case DIALOG_SHORT_WAIT: {
+ ProgressDialog working_dialog = new ProgressDialog(this);
+ working_dialog.setMessage(getResources().getString(
+ R.string.wait_a_moment));
+ working_dialog.setIndeterminate(true);
+ working_dialog.setCancelable(false);
+ dialog = working_dialog;
+ break;
+ }
+ default:
+ dialog = null;
+ }
+ return dialog;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFileStateChanged() {
+ // nothing to do here!
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FileDownloaderBinder getFileDownloaderBinder() {
+ return mDownloaderBinder;
+ }
+
+
+ @Override
+ public FileUploaderBinder getFileUploaderBinder() {
+ return mUploaderBinder;
+ }
+
+
+ @Override
+ public void showFragmentWithDetails(OCFile file) {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
+ startActivity(showDetailsIntent);
+ int pos = mPreviewImagePagerAdapter.getFilePosition(file);
+ file = mPreviewImagePagerAdapter.getFileAt(pos);
+
+ }
+
+
+ private void requestForDownload(OCFile file) {
+ if (mDownloaderBinder == null) {
+ Log.d(TAG, "requestForDownload called without binder to download service");
+
+ } else if (!mDownloaderBinder.isDownloading(mAccount, file)) {
+ Intent i = new Intent(this, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(FileDownloader.EXTRA_FILE, file);
+ startService(i);
+ }
+ }
+
+ /**
+ * This method will be invoked when a new page becomes selected. Animation is not necessarily complete.
+ *
+ * @param Position Position index of the new selected page
+ */
+ @Override
+ public void onPageSelected(int position) {
+ if (mDownloaderBinder == null) {
+ mRequestWaitingForBinder = true;
+
+ } else {
+ OCFile currentFile = mPreviewImagePagerAdapter.getFileAt(position);
+ getSupportActionBar().setTitle(currentFile.getFileName());
+ if (!currentFile.isDown()) {
+ if (!mPreviewImagePagerAdapter.pendingErrorAt(position)) {
+ requestForDownload(currentFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when the scroll state changes. Useful for discovering when the user begins dragging,
+ * when the pager is automatically settling to the current page, or when it is fully stopped/idle.
+ *
+ * @param State The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING
+ */
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+
+ /**
+ * This method will be invoked when the current page is scrolled, either as part of a programmatically
+ * initiated smooth scroll or a user initiated touch scroll.
+ *
+ * @param position Position index of the first page currently being displayed.
+ * Page position+1 will be visible if positionOffset is nonzero.
+ *
+ * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
+ * @param positionOffsetPixels Value in pixels indicating the offset from position.
+ */
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+
+ /**
+ * Class waiting for broadcast events from the {@link FielDownloader} service.
+ *
+ * Updates the UI when a download is started or finished, provided that it is relevant for the
+ * folder displayed in the gallery.
+ */
+ private class DownloadFinishReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
+ String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
+ if (mAccount.name.equals(accountName) &&
+ downloadedRemotePath != null) {
+
+ OCFile file = mStorageManager.getFileByPath(downloadedRemotePath);
+ int position = mPreviewImagePagerAdapter.getFilePosition(file);
+ boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false);
+ //boolean isOffscreen = Math.abs((mViewPager.getCurrentItem() - position)) <= mViewPager.getOffscreenPageLimit();
+
+ if (position >= 0 && intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
+ if (downloadWasFine) {
+ mPreviewImagePagerAdapter.updateFile(position, file);
+
+ } else {
+ mPreviewImagePagerAdapter.updateWithDownloadError(position);
+ }
+ mPreviewImagePagerAdapter.notifyDataSetChanged(); // will trigger the creation of new fragments
+
+ } else {
+ Log.d(TAG, "Download finished, but the fragment is offscreen");
+ }
+
+ }
+ removeStickyBroadcast(intent);
+ }
+
+ }
+
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ toggleFullScreen();
+ }
+ return true;
+ }
+
+
+ private void toggleFullScreen() {
+ ActionBar actionBar = getSupportActionBar();
+ if (mFullScreen) {
+ actionBar.show();
+
+ } else {
+ actionBar.hide();
+
+ }
+ mFullScreen = !mFullScreen;
+ }
+
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.preview;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.util.Log;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.webkit.MimeTypeMap;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragment;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
+import com.owncloud.android.ui.fragment.FileFragment;
+
+import com.owncloud.android.R;
+import eu.alefzero.webdav.WebdavClient;
+import eu.alefzero.webdav.WebdavUtils;
+
+
+/**
+ * This fragment shows a preview of a downloaded image.
+ *
+ * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}.
+ *
+ * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too.
+ *
+ * @author David A. Velasco
+ */
+public class PreviewImageFragment extends SherlockFragment implements FileFragment,
+ OnRemoteOperationListener,
+ ConfirmationDialogFragment.ConfirmationDialogFragmentListener {
+ public static final String EXTRA_FILE = "FILE";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ private View mView;
+ private OCFile mFile;
+ private Account mAccount;
+ private FileDataStorageManager mStorageManager;
+ private ImageView mImageView;
+ private TextView mMessageView;
+ private ProgressBar mProgressWheel;
+
+ public Bitmap mBitmap = null;
+
+ private Handler mHandler;
+ private RemoteOperation mLastRemoteOperation;
+
+ private static final String TAG = PreviewImageFragment.class.getSimpleName();
+
+ private boolean mIgnoreFirstSavedState;
+
+
+ /**
+ * Creates a fragment to preview an image.
+ *
+ * When 'imageFile' or 'ocAccount' are null
+ *
+ * @param imageFile An {@link OCFile} to preview as an image in the fragment
+ * @param ocAccount An ownCloud account; needed to start downloads
+ * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution
+ */
+ public PreviewImageFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) {
+ mFile = fileToDetail;
+ mAccount = ocAccount;
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
+ mIgnoreFirstSavedState = ignoreFirstSavedState;
+ }
+
+
+ /**
+ * Creates an empty fragment for image previews.
+ *
+ * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
+ *
+ * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
+ */
+ public PreviewImageFragment() {
+ mFile = null;
+ mAccount = null;
+ mStorageManager = null;
+ mIgnoreFirstSavedState = false;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mHandler = new Handler();
+ setHasOptionsMenu(true);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ mView = inflater.inflate(R.layout.preview_image_fragment, container, false);
+ mImageView = (ImageView)mView.findViewById(R.id.image);
+ mImageView.setVisibility(View.GONE);
+ mView.setOnTouchListener((OnTouchListener)getActivity()); // WATCH OUT THAT CAST
+ mMessageView = (TextView)mView.findViewById(R.id.message);
+ mMessageView.setVisibility(View.GONE);
+ mProgressWheel = (ProgressBar)mView.findViewById(R.id.progressWheel);
+ mProgressWheel.setVisibility(View.VISIBLE);
+ return mView;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (!(activity instanceof FileFragment.ContainerActivity))
+ throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName());
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
+ if (savedInstanceState != null) {
+ if (!mIgnoreFirstSavedState) {
+ mFile = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT);
+ } else {
+ mIgnoreFirstSavedState = false;
+ }
+ }
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.isDown()) {
+ throw new IllegalStateException("There is no local file to preview");
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(PreviewImageFragment.EXTRA_FILE, mFile);
+ outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount);
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mFile != null) {
+ BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel);
+ bl.execute(new String[]{mFile.getStoragePath()});
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+
+ inflater.inflate(R.menu.file_actions_menu, menu);
+ List<Integer> toHide = new ArrayList<Integer>();
+
+ MenuItem item = null;
+ toHide.add(R.id.action_cancel_download);
+ toHide.add(R.id.action_cancel_upload);
+ toHide.add(R.id.action_download_file);
+ toHide.add(R.id.action_rename_file); // by now
+
+ for (int i : toHide) {
+ item = menu.findItem(i);
+ if (item != null) {
+ item.setVisible(false);
+ item.setEnabled(false);
+ }
+ }
+
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_open_file_with: {
+ openFile();
+ return true;
+ }
+ case R.id.action_remove_file: {
+ removeFile();
+ return true;
+ }
+ case R.id.action_see_details: {
+ seeDetails();
+ return true;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+
+ private void seeDetails() {
+ ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile);
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ //Log.e(TAG, "FRAGMENT, ONRESUME");
+ /*
+ mDownloadFinishReceiver = new DownloadFinishReceiver();
+ IntentFilter filter = new IntentFilter(
+ FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ getActivity().registerReceiver(mDownloadFinishReceiver, filter);
+
+ mUploadFinishReceiver = new UploadFinishReceiver();
+ filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
+ getActivity().registerReceiver(mUploadFinishReceiver, filter);
+ */
+
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ /*
+ if (mVideoPreview.getVisibility() == View.VISIBLE) {
+ mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
+ }*/
+ /*
+ getActivity().unregisterReceiver(mDownloadFinishReceiver);
+ mDownloadFinishReceiver = null;
+
+ getActivity().unregisterReceiver(mUploadFinishReceiver);
+ mUploadFinishReceiver = null;
+ */
+ }
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ }
+
+
+ /**
+ * Opens the previewed image with an external application.
+ *
+ * TODO - improve this; instead of prioritize the actions available for the MIME type in the server,
+ * we should get a list of available apps for MIME tpye in the server and join it with the list of
+ * available apps for the MIME type known from the file extension, to let the user choose
+ */
+ private void openFile() {
+ String storagePath = mFile.getStoragePath();
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+
+ } catch (Throwable t) {
+ Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
+ boolean toastIt = true;
+ String mimeType = "";
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
+ if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
+ if (mimeType != null) {
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+ } else {
+ // desperate try
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*");
+ }
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+ toastIt = false;
+ }
+
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+
+ } catch (Throwable th) {
+ Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+
+ } finally {
+ if (toastIt) {
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ }
+ finish();
+ }
+
+
+ /**
+ * Starts a the removal of the previewed file.
+ *
+ * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)},
+ * depending upon the user selection in the dialog.
+ */
+ private void removeFile() {
+ ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
+ R.string.confirmation_remove_alert,
+ new String[]{mFile.getFileName()},
+ R.string.confirmation_remove_remote_and_local,
+ R.string.confirmation_remove_local,
+ R.string.common_cancel);
+ confDialog.setOnConfirmationListener(this);
+ confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
+ }
+
+
+ /**
+ * Performs the removal of the previewed file, both locally and in the server.
+ */
+ @Override
+ public void onConfirmation(String callerTag) {
+ if (mStorageManager.getFileById(mFile.getFileId()) != null) { // check that the file is still there;
+ mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters
+ true,
+ mStorageManager);
+ WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
+ mLastRemoteOperation.execute(wc, this, mHandler);
+
+ getActivity().showDialog(PreviewImageActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+
+
+ /**
+ * Removes the file from local storage
+ */
+ @Override
+ public void onNeutral(String callerTag) {
+ // TODO this code should be made in a secondary thread,
+ if (mFile.isDown()) { // checks it is still there
+ File f = new File(mFile.getStoragePath());
+ f.delete();
+ mFile.setStoragePath(null);
+ mStorageManager.saveFile(mFile);
+ finish();
+ }
+ }
+
+ /**
+ * User cancelled the removal action.
+ */
+ @Override
+ public void onCancel(String callerTag) {
+ // nothing to do here
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+ /*
+ /**
+ * Use this method to signal this Activity that it shall update its view.
+ *
+ * @param file : An {@link OCFile}
+ *-/
+ public void updateFileDetails(OCFile file, Account ocAccount) {
+ mFile = file;
+ if (ocAccount != null && (
+ mStorageManager == null ||
+ (mAccount != null && !mAccount.equals(ocAccount))
+ )) {
+ mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
+ }
+ mAccount = ocAccount;
+ updateFileDetails(false);
+ }
+ */
+
+
+ private class BitmapLoader extends AsyncTask<String, Void, Bitmap> {
+
+ /**
+ * Weak reference to the target {@link ImageView} where the bitmap will be loaded into.
+ *
+ * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
+ */
+ private final WeakReference<ImageView> mImageViewRef;
+
+ /**
+ * Weak reference to the target {@link TextView} where error messages will be written.
+ *
+ * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
+ */
+ private final WeakReference<TextView> mMessageViewRef;
+
+
+ /**
+ * Weak reference to the target {@link Progressbar} shown while the load is in progress.
+ *
+ * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
+ */
+ private final WeakReference<ProgressBar> mProgressWheelRef;
+
+
+ /**
+ * Error message to show when a load fails
+ */
+ private int mErrorMessageId;
+
+
+ /**
+ * Constructor.
+ *
+ * @param imageView Target {@link ImageView} where the bitmap will be loaded into.
+ */
+ public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) {
+ mImageViewRef = new WeakReference<ImageView>(imageView);
+ mMessageViewRef = new WeakReference<TextView>(messageView);
+ mProgressWheelRef = new WeakReference<ProgressBar>(progressWheel);
+ }
+
+
+ @SuppressWarnings("deprecation")
+ @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20
+ @Override
+ protected Bitmap doInBackground(String... params) {
+ Bitmap result = null;
+ if (params.length != 1) return result;
+ String storagePath = params[0];
+ try {
+ // set desired options that will affect the size of the bitmap
+ BitmapFactory.Options options = new Options();
+ options.inScaled = true;
+ options.inPurgeable = true;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ options.inPreferQualityOverSpeed = false;
+ }
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ options.inMutable = false;
+ }
+ // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(storagePath, options);
+
+ int width = options.outWidth;
+ int height = options.outHeight;
+ int scale = 1;
+
+ Display display = getActivity().getWindowManager().getDefaultDisplay();
+ Point size = new Point();
+ int screenWidth;
+ int screenHeight;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) {
+ display.getSize(size);
+ screenWidth = size.x;
+ screenHeight = size.y;
+ } else {
+ screenWidth = display.getWidth();
+ screenHeight = display.getHeight();
+ }
+
+ if (width > screenWidth) {
+ // second try to scale down the image , this time depending upon the screen size
+ scale = (int) Math.floor((float)width / screenWidth);
+ }
+ if (height > screenHeight) {
+ scale = Math.max(scale, (int) Math.floor((float)height / screenHeight));
+ }
+ options.inSampleSize = scale;
+
+ // really load the bitmap
+ options.inJustDecodeBounds = false; // the next decodeFile call will be real
+ result = BitmapFactory.decodeFile(storagePath, options);
+ //Log.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight);
+
+ if (result == null) {
+ mErrorMessageId = R.string.preview_image_error_unknown_format;
+ Log.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
+ }
+
+ } catch (OutOfMemoryError e) {
+ mErrorMessageId = R.string.preview_image_error_unknown_format;
+ Log.e(TAG, "Out of memory occured for file " + storagePath, e);
+
+ } catch (NoSuchFieldError e) {
+ mErrorMessageId = R.string.common_error_unknown;
+ Log.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e);
+
+ } catch (Throwable t) {
+ mErrorMessageId = R.string.common_error_unknown;
+ Log.e(TAG, "Unexpected error loading " + mFile.getStoragePath(), t);
+
+ }
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ hideProgressWheel();
+ if (result != null) {
+ showLoadedImage(result);
+ } else {
+ showErrorMessage();
+ }
+ }
+
+ private void showLoadedImage(Bitmap result) {
+ if (mImageViewRef != null) {
+ final ImageView imageView = mImageViewRef.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(result);
+ imageView.setVisibility(View.VISIBLE);
+ mBitmap = result;
+ } // else , silently finish, the fragment was destroyed
+ }
+ if (mMessageViewRef != null) {
+ final TextView messageView = mMessageViewRef.get();
+ if (messageView != null) {
+ messageView.setVisibility(View.GONE);
+ } // else , silently finish, the fragment was destroyed
+ }
+ }
+
+ private void showErrorMessage() {
+ if (mImageViewRef != null) {
+ final ImageView imageView = mImageViewRef.get();
+ if (imageView != null) {
+ // shows the default error icon
+ imageView.setVisibility(View.VISIBLE);
+ } // else , silently finish, the fragment was destroyed
+ }
+ if (mMessageViewRef != null) {
+ final TextView messageView = mMessageViewRef.get();
+ if (messageView != null) {
+ messageView.setText(mErrorMessageId);
+ messageView.setVisibility(View.VISIBLE);
+ } // else , silently finish, the fragment was destroyed
+ }
+ }
+
+ private void hideProgressWheel() {
+ if (mProgressWheelRef != null) {
+ final ProgressBar progressWheel = mProgressWheelRef.get();
+ if (progressWheel != null) {
+ progressWheel.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed.
+ *
+ * @param file File to test if can be previewed.
+ * @return 'True' if the file can be handled by the fragment.
+ */
+ public static boolean canBePreviewed(OCFile file) {
+ return (file != null && file.isImage());
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+ if (operation.equals(mLastRemoteOperation) && operation instanceof RemoveFileOperation) {
+ onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
+ }
+ }
+
+ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
+ getActivity().dismissDialog(PreviewImageActivity.DIALOG_SHORT_WAIT);
+
+ if (result.isSuccess()) {
+ Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);
+ msg.show();
+ finish();
+
+ } else {
+ Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG);
+ msg.show();
+ if (result.isSslRecoverableException()) {
+ // TODO show the SSL warning dialog
+ }
+ }
+ }
+
+ /**
+ * Finishes the preview
+ */
+ private void finish() {
+ Activity container = getActivity();
+ container.finish();
+ }
+
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.preview;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.owncloud.android.datamodel.DataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.fragment.FileFragment;
+
+/**
+ * Adapter class that provides Fragment instances
+ *
+ * @author David A. Velasco
+ */
+//public class PreviewImagePagerAdapter extends PagerAdapter {
+public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
+
+ private static final String TAG = PreviewImagePagerAdapter.class.getSimpleName();
+
+ private Vector<OCFile> mImageFiles;
+ private Account mAccount;
+ private Set<Object> mObsoleteFragments;
+ private Set<Integer> mObsoletePositions;
+ private Set<Integer> mDownloadErrors;
+ private DataStorageManager mStorageManager;
+
+ private Map<Integer, FileFragment> mCachedFragments;
+
+ /*
+ private final FragmentManager mFragmentManager;
+ private FragmentTransaction mCurTransaction = null;
+ private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
+ private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+ private Fragment mCurrentPrimaryItem = null;
+ */
+
+ /**
+ * Constructor.
+ *
+ * @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the adapter.
+ * @param parentFolder Folder where images will be searched for.
+ * @param storageManager Bridge to database.
+ */
+ public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, DataStorageManager storageManager) {
+ super(fragmentManager);
+
+ if (fragmentManager == null) {
+ throw new IllegalArgumentException("NULL FragmentManager instance");
+ }
+ if (parentFolder == null) {
+ throw new IllegalArgumentException("NULL parent folder");
+ }
+ if (storageManager == null) {
+ throw new IllegalArgumentException("NULL storage manager");
+ }
+
+ mAccount = account;
+ mStorageManager = storageManager;
+ mImageFiles = mStorageManager.getDirectoryImages(parentFolder);
+ mObsoleteFragments = new HashSet<Object>();
+ mObsoletePositions = new HashSet<Integer>();
+ mDownloadErrors = new HashSet<Integer>();
+ //mFragmentManager = fragmentManager;
+ mCachedFragments = new HashMap<Integer, FileFragment>();
+ }
+
+
+ /**
+ * Returns the image files handled by the adapter.
+ *
+ * @return A vector with the image files handled by the adapter.
+ */
+ protected OCFile getFileAt(int position) {
+ return mImageFiles.get(position);
+ }
+
+
+ public Fragment getItem(int i) {
+ OCFile file = mImageFiles.get(i);
+ Fragment fragment = null;
+ if (file.isDown()) {
+ fragment = new PreviewImageFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i)));
+
+ } else if (mDownloadErrors.contains(Integer.valueOf(i))) {
+ fragment = new FileDownloadFragment(file, mAccount, true);
+ ((FileDownloadFragment)fragment).setError(true);
+ mDownloadErrors.remove(Integer.valueOf(i));
+
+ } else {
+ fragment = new FileDownloadFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i)));
+ }
+ mObsoletePositions.remove(Integer.valueOf(i));
+ return fragment;
+ }
+
+ public int getFilePosition(OCFile file) {
+ return mImageFiles.indexOf(file);
+ }
+
+ @Override
+ public int getCount() {
+ return mImageFiles.size();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mImageFiles.get(position).getFileName();
+ }
+
+
+ public void updateFile(int position, OCFile file) {
+ FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position));
+ if (fragmentToUpdate != null) {
+ mObsoleteFragments.add(fragmentToUpdate);
+ }
+ mObsoletePositions.add(Integer.valueOf(position));
+ mImageFiles.set(position, file);
+ }
+
+
+ public void updateWithDownloadError(int position) {
+ FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position));
+ if (fragmentToUpdate != null) {
+ mObsoleteFragments.add(fragmentToUpdate);
+ }
+ mDownloadErrors.add(Integer.valueOf(position));
+ }
+
+ public void clearErrorAt(int position) {
+ FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position));
+ if (fragmentToUpdate != null) {
+ mObsoleteFragments.add(fragmentToUpdate);
+ }
+ mDownloadErrors.remove(Integer.valueOf(position));
+ }
+
+
+ @Override
+ public int getItemPosition(Object object) {
+ if (mObsoleteFragments.contains(object)) {
+ mObsoleteFragments.remove(object);
+ return POSITION_NONE;
+ }
+ return super.getItemPosition(object);
+ }
+
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ Object fragment = super.instantiateItem(container, position);
+ mCachedFragments.put(Integer.valueOf(position), (FileFragment)fragment);
+ return fragment;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ mCachedFragments.remove(Integer.valueOf(position));
+ super.destroyItem(container, position, object);
+ }
+
+
+ public boolean pendingErrorAt(int position) {
+ return mDownloadErrors.contains(Integer.valueOf(position));
+ }
+
+
+
+ /* -*
+ * Called when a change in the shown pages is going to start being made.
+ *
+ * @param container The containing View which is displaying this adapter's page views.
+ *- /
+ @Override
+ public void startUpdate(ViewGroup container) {
+ Log.e(TAG, "** startUpdate");
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ Log.e(TAG, "** instantiateItem " + position);
+
+ if (mFragments.size() > position) {
+ Fragment fragment = mFragments.get(position);
+ if (fragment != null) {
+ Log.e(TAG, "** \t returning cached item");
+ return fragment;
+ }
+ }
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+
+ Fragment fragment = getItem(position);
+ if (mSavedState.size() > position) {
+ Fragment.SavedState savedState = mSavedState.get(position);
+ if (savedState != null) {
+ // TODO WATCH OUT:
+ // * The Fragment must currently be attached to the FragmentManager.
+ // * A new Fragment created using this saved state must be the same class type as the Fragment it was created from.
+ // * The saved state can not contain dependencies on other fragments -- that is it can't use putFragment(Bundle, String, Fragment)
+ // to store a fragment reference
+ fragment.setInitialSavedState(savedState);
+ }
+ }
+ while (mFragments.size() <= position) {
+ mFragments.add(null);
+ }
+ fragment.setMenuVisibility(false);
+ mFragments.set(position, fragment);
+ //Log.e(TAG, "** \t adding fragment at position " + position + ", containerId " + container.getId());
+ mCurTransaction.add(container.getId(), fragment);
+
+ return fragment;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ Log.e(TAG, "** destroyItem " + position);
+ Fragment fragment = (Fragment)object;
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ Log.e(TAG, "** \t removing fragment at position " + position);
+ while (mSavedState.size() <= position) {
+ mSavedState.add(null);
+ }
+ mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+ mFragments.set(position, null);
+
+ mCurTransaction.remove(fragment);
+ }
+
+ @Override
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ Fragment fragment = (Fragment)object;
+ if (fragment != mCurrentPrimaryItem) {
+ if (mCurrentPrimaryItem != null) {
+ mCurrentPrimaryItem.setMenuVisibility(false);
+ }
+ if (fragment != null) {
+ fragment.setMenuVisibility(true);
+ }
+ mCurrentPrimaryItem = fragment;
+ }
+ }
+
+ @Override
+ public void finishUpdate(ViewGroup container) {
+ Log.e(TAG, "** finishUpdate (start)");
+ if (mCurTransaction != null) {
+ mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction = null;
+ mFragmentManager.executePendingTransactions();
+ }
+ Log.e(TAG, "** finishUpdate (end)");
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return ((Fragment)object).getView() == view;
+ }
+
+ @Override
+ public Parcelable saveState() {
+ Bundle state = null;
+ if (mSavedState.size() > 0) {
+ state = new Bundle();
+ Fragment.SavedState[] savedStates = new Fragment.SavedState[mSavedState.size()];
+ mSavedState.toArray(savedStates);
+ state.putParcelableArray("states", savedStates);
+ }
+ for (int i=0; i<mFragments.size(); i++) {
+ Fragment fragment = mFragments.get(i);
+ if (fragment != null) {
+ if (state == null) {
+ state = new Bundle();
+ }
+ String key = "f" + i;
+ mFragmentManager.putFragment(state, key, fragment);
+ }
+ }
+ return state;
+ }
+
+ @Override
+ public void restoreState(Parcelable state, ClassLoader loader) {
+ if (state != null) {
+ Bundle bundle = (Bundle)state;
+ bundle.setClassLoader(loader);
+ Parcelable[] states = bundle.getParcelableArray("states");
+ mSavedState.clear();
+ mFragments.clear();
+ if (states != null) {
+ for (int i=0; i<states.length; i++) {
+ mSavedState.add((Fragment.SavedState)states[i]);
+ }
+ }
+ Iterable<String> keys = bundle.keySet();
+ for (String key: keys) {
+ if (key.startsWith("f")) {
+ int index = Integer.parseInt(key.substring(1));
+ Fragment f = mFragmentManager.getFragment(bundle, key);
+ if (f != null) {
+ while (mFragments.size() <= index) {
+ mFragments.add(null);
+ }
+ f.setMenuVisibility(false);
+ mFragments.set(index, f);
+ } else {
+ Log.w(TAG, "Bad fragment at key " + key);
+ }
+ }
+ }
+ }
+ }
+ */
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.preview;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.webkit.MimeTypeMap;
+import android.widget.ImageView;
+import android.widget.Toast;
+import android.widget.VideoView;
+
+import com.actionbarsherlock.app.SherlockFragment;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuInflater;
+import com.actionbarsherlock.view.MenuItem;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.media.MediaControlView;
+import com.owncloud.android.media.MediaService;
+import com.owncloud.android.media.MediaServiceBinder;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.ui.activity.FileDetailActivity;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
+import com.owncloud.android.ui.fragment.FileDetailFragment;
+import com.owncloud.android.ui.fragment.FileFragment;
+
+import com.owncloud.android.R;
+import eu.alefzero.webdav.WebdavClient;
+import eu.alefzero.webdav.WebdavUtils;
+
+/**
+ * This fragment shows a preview of a downloaded media file (audio or video).
+ *
+ * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}.
+ *
+ * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too.
+ *
+ * @author David A. Velasco
+ */
+public class PreviewMediaFragment extends SherlockFragment implements
+ OnTouchListener , FileFragment,
+ ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener {
+
+ public static final String EXTRA_FILE = "FILE";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+ private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
+ private static final String EXTRA_PLAYING = "PLAYING";
+
+ private View mView;
+ private OCFile mFile;
+ private Account mAccount;
+ private FileDataStorageManager mStorageManager;
+ private ImageView mImagePreview;
+ private VideoView mVideoPreview;
+ private int mSavedPlaybackPosition;
+
+ private Handler mHandler;
+ private RemoteOperation mLastRemoteOperation;
+
+ private MediaServiceBinder mMediaServiceBinder = null;
+ private MediaControlView mMediaController = null;
+ private MediaServiceConnection mMediaServiceConnection = null;
+ private VideoHelper mVideoHelper;
+ private boolean mAutoplay;
+
+ private static final String TAG = PreviewMediaFragment.class.getSimpleName();
+
+
+ /**
+ * Creates a fragment to preview a file.
+ *
+ * When 'fileToDetail' or 'ocAccount' are null
+ *
+ * @param fileToDetail An {@link OCFile} to preview in the fragment
+ * @param ocAccount An ownCloud account; needed to start downloads
+ */
+ public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount) {
+ mFile = fileToDetail;
+ mAccount = ocAccount;
+ mSavedPlaybackPosition = 0;
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
+ mAutoplay = true;
+ }
+
+
+ /**
+ * Creates an empty fragment for previews.
+ *
+ * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
+ *
+ * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
+ */
+ public PreviewMediaFragment() {
+ mFile = null;
+ mAccount = null;
+ mSavedPlaybackPosition = 0;
+ mStorageManager = null;
+ mAutoplay = true;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mHandler = new Handler();
+ setHasOptionsMenu(true);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ mView = inflater.inflate(R.layout.file_preview, container, false);
+
+ mImagePreview = (ImageView)mView.findViewById(R.id.image_preview);
+ mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview);
+ mVideoPreview.setOnTouchListener(this);
+
+ mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller);
+
+ return mView;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (!(activity instanceof FileFragment.ContainerActivity))
+ throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName());
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
+ if (savedInstanceState != null) {
+ mFile = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
+ mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
+ mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING);
+
+ }
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.isDown()) {
+ throw new IllegalStateException("There is no local file to preview");
+ }
+ if (mFile.isVideo()) {
+ mVideoPreview.setVisibility(View.VISIBLE);
+ mImagePreview.setVisibility(View.GONE);
+ prepareVideo();
+
+ } else {
+ mVideoPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.VISIBLE);
+ }
+
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, mFile);
+ outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount);
+
+ if (mFile.isVideo()) {
+ outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mVideoPreview.getCurrentPosition());
+ outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mVideoPreview.isPlaying());
+ } else {
+ outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mMediaServiceBinder.getCurrentPosition());
+ outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mMediaServiceBinder.isPlaying());
+ }
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if (mFile != null) {
+ if (mFile.isAudio()) {
+ bindMediaService();
+
+ } else if (mFile.isVideo()) {
+ stopAudio();
+ playVideo();
+ }
+ }
+ }
+
+
+ private void stopAudio() {
+ Intent i = new Intent(getSherlockActivity(), MediaService.class);
+ i.setAction(MediaService.ACTION_STOP_ALL);
+ getSherlockActivity().startService(i);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+
+ inflater.inflate(R.menu.file_actions_menu, menu);
+ List<Integer> toHide = new ArrayList<Integer>();
+
+ MenuItem item = null;
+ toHide.add(R.id.action_cancel_download);
+ toHide.add(R.id.action_cancel_upload);
+ toHide.add(R.id.action_download_file);
+ toHide.add(R.id.action_rename_file); // by now
+
+ for (int i : toHide) {
+ item = menu.findItem(i);
+ if (item != null) {
+ item.setVisible(false);
+ item.setEnabled(false);
+ }
+ }
+
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_open_file_with: {
+ openFile();
+ return true;
+ }
+ case R.id.action_remove_file: {
+ removeFile();
+ return true;
+ }
+ case R.id.action_see_details: {
+ seeDetails();
+ return true;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+
+ private void seeDetails() {
+ stopPreview(false);
+ ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile);
+ }
+
+
+ private void prepareVideo() {
+ // create helper to get more control on the playback
+ mVideoHelper = new VideoHelper();
+ mVideoPreview.setOnPreparedListener(mVideoHelper);
+ mVideoPreview.setOnCompletionListener(mVideoHelper);
+ mVideoPreview.setOnErrorListener(mVideoHelper);
+ }
+
+ private void playVideo() {
+ // create and prepare control panel for the user
+ mMediaController.setMediaPlayer(mVideoPreview);
+
+ // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called
+ mVideoPreview.setVideoPath(mFile.getStoragePath());
+ }
+
+
+ private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
+
+ /**
+ * Called when the file is ready to be played.
+ *
+ * Just starts the playback.
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ */
+ @Override
+ public void onPrepared(MediaPlayer vp) {
+ mVideoPreview.seekTo(mSavedPlaybackPosition);
+ if (mAutoplay) {
+ mVideoPreview.start();
+ }
+ mMediaController.setEnabled(true);
+ mMediaController.updatePausePlay();
+ }
+
+
+ /**
+ * Called when the file is finished playing.
+ *
+ * Finishes the activity.
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ */
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mVideoPreview.seekTo(0);
+ mMediaController.updatePausePlay();
+ }
+
+
+ /**
+ * Called when an error in playback occurs.
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ * @param what Type of error
+ * @param extra Extra code specific to the error
+ */
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ if (mVideoPreview.getWindowToken() != null) {
+ String message = MediaService.getMessageForMediaError(getActivity(), what, extra);
+ new AlertDialog.Builder(getActivity())
+ .setMessage(message)
+ .setPositiveButton(android.R.string.VideoView_error_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dialog.dismiss();
+ VideoHelper.this.onCompletion(null);
+ }
+ })
+ .setCancelable(false)
+ .show();
+ }
+ return true;
+ }
+
+ }
+
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ if (mMediaServiceConnection != null) {
+ Log.d(TAG, "Unbinding from MediaService ...");
+ if (mMediaServiceBinder != null && mMediaController != null) {
+ mMediaServiceBinder.unregisterMediaController(mMediaController);
+ }
+ getActivity().unbindService(mMediaServiceConnection);
+ mMediaServiceConnection = null;
+ mMediaServiceBinder = null;
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) {
+ startFullScreenVideo();
+ return true;
+ }
+ return false;
+ }
+
+
+ private void startFullScreenVideo() {
+ Intent i = new Intent(getActivity(), PreviewVideoActivity.class);
+ i.putExtra(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(PreviewVideoActivity.EXTRA_FILE, mFile);
+ i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
+ mVideoPreview.pause();
+ i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
+ startActivityForResult(i, 0);
+ }
+
+
+ @Override
+ public void onActivityResult (int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == Activity.RESULT_OK) {
+ mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION);
+ mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY);
+ }
+ }
+
+
+ private void playAudio() {
+ if (!mMediaServiceBinder.isPlaying(mFile)) {
+ Log.d(TAG, "starting playback of " + mFile.getStoragePath());
+ mMediaServiceBinder.start(mAccount, mFile, mAutoplay, mSavedPlaybackPosition);
+
+ } else {
+ if (!mMediaServiceBinder.isPlaying() && mAutoplay) {
+ mMediaServiceBinder.start();
+ mMediaController.updatePausePlay();
+ }
+ }
+ }
+
+
+ private void bindMediaService() {
+ Log.d(TAG, "Binding to MediaService...");
+ if (mMediaServiceConnection == null) {
+ mMediaServiceConnection = new MediaServiceConnection();
+ }
+ getActivity().bindService( new Intent(getActivity(),
+ MediaService.class),
+ mMediaServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ // follow the flow in MediaServiceConnection#onServiceConnected(...)
+ }
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private class MediaServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (component.equals(new ComponentName(getActivity(), MediaService.class))) {
+ Log.d(TAG, "Media service connected");
+ mMediaServiceBinder = (MediaServiceBinder) service;
+ if (mMediaServiceBinder != null) {
+ prepareMediaController();
+ playAudio(); // do not wait for the touch of nobody to play audio
+
+ Log.d(TAG, "Successfully bound to MediaService, MediaController ready");
+
+ } else {
+ Log.e(TAG, "Unexpected response from MediaService while binding");
+ }
+ }
+ }
+
+ private void prepareMediaController() {
+ mMediaServiceBinder.registerMediaController(mMediaController);
+ if (mMediaController != null) {
+ mMediaController.setMediaPlayer(mMediaServiceBinder);
+ mMediaController.setEnabled(true);
+ mMediaController.updatePausePlay();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (component.equals(new ComponentName(getActivity(), MediaService.class))) {
+ Log.e(TAG, "Media service suddenly disconnected");
+ if (mMediaController != null) {
+ mMediaController.setMediaPlayer(null);
+ } else {
+ Toast.makeText(getActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show();
+ }
+ mMediaServiceBinder = null;
+ mMediaServiceConnection = null;
+ }
+ }
+ }
+
+
+
+ /**
+ * Opens the previewed file with an external application.
+ *
+ * TODO - improve this; instead of prioritize the actions available for the MIME type in the server,
+ * we should get a list of available apps for MIME tpye in the server and join it with the list of
+ * available apps for the MIME type known from the file extension, to let the user choose
+ */
+ private void openFile() {
+ stopPreview(true);
+ String storagePath = mFile.getStoragePath();
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+
+ } catch (Throwable t) {
+ Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
+ boolean toastIt = true;
+ String mimeType = "";
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
+ if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
+ if (mimeType != null) {
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+ } else {
+ // desperate try
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*");
+ }
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+ toastIt = false;
+ }
+
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+
+ } catch (Throwable th) {
+ Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+
+ } finally {
+ if (toastIt) {
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ }
+ finish();
+ }
+
+ /**
+ * Starts a the removal of the previewed file.
+ *
+ * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)},
+ * depending upon the user selection in the dialog.
+ */
+ private void removeFile() {
+ ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
+ R.string.confirmation_remove_alert,
+ new String[]{mFile.getFileName()},
+ R.string.confirmation_remove_remote_and_local,
+ R.string.confirmation_remove_local,
+ R.string.common_cancel);
+ confDialog.setOnConfirmationListener(this);
+ confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
+ }
+
+
+ /**
+ * Performs the removal of the previewed file, both locally and in the server.
+ */
+ @Override
+ public void onConfirmation(String callerTag) {
+ if (mStorageManager.getFileById(mFile.getFileId()) != null) { // check that the file is still there;
+ stopPreview(true);
+ mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters
+ true,
+ mStorageManager);
+ WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
+ mLastRemoteOperation.execute(wc, this, mHandler);
+
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+
+
+ /**
+ * Removes the file from local storage
+ */
+ @Override
+ public void onNeutral(String callerTag) {
+ // TODO this code should be made in a secondary thread,
+ if (mFile.isDown()) { // checks it is still there
+ stopPreview(true);
+ File f = new File(mFile.getStoragePath());
+ f.delete();
+ mFile.setStoragePath(null);
+ mStorageManager.saveFile(mFile);
+ finish();
+ }
+ }
+
+ /**
+ * User cancelled the removal action.
+ */
+ @Override
+ public void onCancel(String callerTag) {
+ // nothing to do here
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+ /*
+ /**
+ * Use this method to signal this Activity that it shall update its view.
+ *
+ * @param file : An {@link OCFile}
+ *-/
+ public void updateFileDetails(OCFile file, Account ocAccount) {
+ mFile = file;
+ if (ocAccount != null && (
+ mStorageManager == null ||
+ (mAccount != null && !mAccount.equals(ocAccount))
+ )) {
+ mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
+ }
+ mAccount = ocAccount;
+ updateFileDetails(false);
+ }
+ */
+
+
+ /**
+ * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
+ *
+ * @param file File to test if can be previewed.
+ * @return 'True' if the file can be handled by the fragment.
+ */
+ public static boolean canBePreviewed(OCFile file) {
+ return (file != null && (file.isAudio() || file.isVideo()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+ if (operation.equals(mLastRemoteOperation)) {
+ if (operation instanceof RemoveFileOperation) {
+ onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
+ }
+ }
+ }
+
+ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+ if (result.isSuccess()) {
+ Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);
+ msg.show();
+ finish();
+
+ } else {
+ Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG);
+ msg.show();
+ if (result.isSslRecoverableException()) {
+ // TODO show the SSL warning dialog
+ }
+ }
+ }
+
+ private void stopPreview(boolean stopAudio) {
+ if (mFile.isAudio() && stopAudio) {
+ mMediaServiceBinder.pause();
+
+ } else if (mFile.isVideo()) {
+ mVideoPreview.stopPlayback();
+ }
+ }
+
+
+
+ /**
+ * Finishes the preview
+ */
+ private void finish() {
+ Activity container = getActivity();
+ if (container instanceof FileDisplayActivity) {
+ // double pane
+ FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
+ transaction.commit();
+ ((FileFragment.ContainerActivity)container).onFileStateChanged();
+ } else {
+ container.finish();
+ }
+ }
+
+}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui.preview;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.media.MediaService;
+
+/**
+ * Activity implementing a basic video player.
+ *
+ * Used as an utility to preview video files contained in an ownCloud account.
+ *
+ * Currently, it always plays in landscape mode, full screen. When the playback ends,
+ * the activity is finished.
+ *
+ * @author David A. Velasco
+ */
+public class PreviewVideoActivity extends Activity implements OnCompletionListener, OnPreparedListener, OnErrorListener {
+
+ /** Key to receive an {@link OCFile} to play as an extra value in an {@link Intent} */
+ public static final String EXTRA_FILE = "FILE";
+
+ /** Key to receive the ownCloud {@link Account} where the file to play is saved as an extra value in an {@link Intent} */
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ /** Key to receive a flag signaling if the video should be started immediately */
+ public static final String EXTRA_AUTOPLAY = "AUTOPLAY";
+
+ /** Key to receive the position of the playback where the video should be put at start */
+ public static final String EXTRA_START_POSITION = "START_POSITION";
+
+ private static final String TAG = PreviewVideoActivity.class.getSimpleName();
+
+ private OCFile mFile; // video file to play
+ private Account mAccount; // ownCloud account holding mFile
+ private int mSavedPlaybackPosition; // in the unit time handled by MediaPlayer.getCurrentPosition()
+ private boolean mAutoplay; // when 'true', the playback starts immediately with the activity
+ private VideoView mVideoPlayer; // view to play the file; both performs and show the playback
+ private MediaController mMediaController; // panel control used by the user to control the playback
+
+ /**
+ * Called when the activity is first created.
+ *
+ * Searches for an {@link OCFile} and ownCloud {@link Account} holding it in the starting {@link Intent}.
+ *
+ * The {@link Account} is unnecessary if the file is downloaded; else, the {@link Account} is used to
+ * try to stream the remote file - TODO get the streaming works
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.e(TAG, "ACTIVITY\t\tonCreate");
+
+ setContentView(R.layout.video_layout);
+
+ if (savedInstanceState == null) {
+ Bundle extras = getIntent().getExtras();
+ mFile = extras.getParcelable(EXTRA_FILE);
+ mAccount = extras.getParcelable(EXTRA_ACCOUNT);
+ mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION);
+ mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY);
+
+ } else {
+ mFile = savedInstanceState.getParcelable(EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT);
+ mSavedPlaybackPosition = savedInstanceState.getInt(EXTRA_START_POSITION);
+ mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY);
+ }
+
+ mVideoPlayer = (VideoView) findViewById(R.id.videoPlayer);
+
+ // set listeners to get more contol on the playback
+ mVideoPlayer.setOnPreparedListener(this);
+ mVideoPlayer.setOnCompletionListener(this);
+ mVideoPlayer.setOnErrorListener(this);
+
+ // keep the screen on while the playback is performed (prevents screen off by battery save)
+ mVideoPlayer.setKeepScreenOn(true);
+
+ if (mFile != null) {
+ if (mFile.isDown()) {
+ mVideoPlayer.setVideoPath(mFile.getStoragePath());
+
+ } else if (mAccount != null) {
+ // not working now
+ String url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
+ mVideoPlayer.setVideoURI(Uri.parse(url));
+
+ } else {
+ onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account);
+ }
+
+ // create and prepare control panel for the user
+ mMediaController = new MediaController(this);
+ mMediaController.setMediaPlayer(mVideoPlayer);
+ mMediaController.setAnchorView(mVideoPlayer);
+ mVideoPlayer.setMediaController(mMediaController);
+
+ } else {
+ onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_nothing_to_play);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ Log.e(TAG, "ACTIVITY\t\tonSaveInstanceState");
+ outState.putParcelable(PreviewVideoActivity.EXTRA_FILE, mFile);
+ outState.putParcelable(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount);
+ outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition());
+ outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying());
+ }
+
+
+ @Override
+ public void onBackPressed() {
+ Log.e(TAG, "ACTIVTIY\t\tonBackPressed");
+ Intent i = new Intent();
+ i.putExtra(EXTRA_AUTOPLAY, mVideoPlayer.isPlaying());
+ i.putExtra(EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition());
+ setResult(RESULT_OK, i);
+ super.onBackPressed();
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Log.e(TAG, "ACTIVTIY\t\tonResume");
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Log.e(TAG, "ACTIVTIY\t\tonStart");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.e(TAG, "ACTIVITY\t\tonDestroy");
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ Log.e(TAG, "ACTIVTIY\t\tonStop");
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ Log.e(TAG, "ACTIVTIY\t\tonPause");
+ }
+
+
+ /**
+ * Called when the file is ready to be played.
+ *
+ * Just starts the playback.
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ */
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ Log.e(TAG, "ACTIVITY\t\tonPrepare");
+ mVideoPlayer.seekTo(mSavedPlaybackPosition);
+ if (mAutoplay) {
+ mVideoPlayer.start();
+ }
+ mMediaController.show(5000);
+ }
+
+
+ /**
+ * Called when the file is finished playing.
+ *
+ * Rewinds the video
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ */
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mVideoPlayer.seekTo(0);
+ }
+
+
+ /**
+ * Called when an error in playback occurs.
+ *
+ * @param mp {@link MediaPlayer} instance performing the playback.
+ * @param what Type of error
+ * @param extra Extra code specific to the error
+ */
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
+
+ if (mMediaController != null) {
+ mMediaController.hide();
+ }
+
+ if (mVideoPlayer.getWindowToken() != null) {
+ String message = MediaService.getMessageForMediaError(this, what, extra);
+ new AlertDialog.Builder(this)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.VideoView_error_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ PreviewVideoActivity.this.onCompletion(null);
+ }
+ })
+ .setCancelable(false)
+ .show();
+ }
+ return true;
+ }
+
+
+ /**
+ * Screen touches trigger the appearance of the control panel for a limited time.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTouchEvent (MotionEvent ev){
+ /*if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mMediaController.isShowing()) {
+ mMediaController.hide();
+ } else {
+ mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE);
+ }
+ return true;
+ } else {
+ return false;
+ }*/
+ return false;
+ }
+
+
+}
\ No newline at end of file
import java.io.File;
+import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
+import android.util.Log;
import com.owncloud.android.datamodel.OCFile;
-
+import com.owncloud.android.files.services.InstantUploadService;
/**
* Static methods to help in access to local file system.
* @author David A. Velasco
*/
public class FileStorageUtils {
-
+ private static final String LOG_TAG = "FileStorageUtils";
+
public static final String getSavePath(String accountName) {
File sdCard = Environment.getExternalStorageDirectory();
- return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@");
- // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
+ return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@");
+ // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
}
-
+
public static final String getDefaultSavePathFor(String accountName, OCFile file) {
return getSavePath(accountName) + file.getRemotePath();
}
-
+
public static final String getTemporalPath(String accountName) {
File sdCard = Environment.getExternalStorageDirectory();
return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@");
// URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
}
+ @SuppressLint("NewApi")
public static final long getUsableSpace(String accountName) {
File savePath = Environment.getExternalStorageDirectory();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
return savePath.getUsableSpace();
-
+
} else {
StatFs stats = new StatFs(savePath.getAbsolutePath());
return stats.getAvailableBlocks() * stats.getBlockSize();
}
-
+
+ }
+
+ // to ensure we will not add the slash twice between filename and
+ // folder-name
+ private static String getFileName(String filepath) {
+ if (filepath != null && !"".equals(filepath)) {
+ int psi = filepath.lastIndexOf('/');
+ String filename = filepath;
+ if (psi > -1) {
+ filename = filepath.substring(psi + 1, filepath.length());
+ Log.d(LOG_TAG, "extracted filename :" + filename);
+ }
+ return filename;
+ } else {
+ // Toast
+ Log.w(LOG_TAG, "the given filename was null or empty");
+ return null;
+ }
+ }
+
+ public static String getInstantUploadFilePath(String fileName) {
+ return InstantUploadService.INSTANT_UPLOAD_DIR + "/" + getFileName(fileName);
}
-
}
\ No newline at end of file
import org.apache.commons.httpclient.methods.RequestEntity;
+import com.owncloud.android.network.ProgressiveDataTransferer;
+
import eu.alefzero.webdav.OnDatatransferProgressListener;
import android.util.Log;
*
* @author David A. Velasco
*/
-public class ChunkFromFileChannelRequestEntity implements RequestEntity {
+public class ChunkFromFileChannelRequestEntity implements RequestEntity, ProgressiveDataTransferer {
private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName();
return true;
}
- public void addOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
- mDataTransferListeners.add(listener);
+ @Override
+ public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.add(listener);
+ }
}
- public void addOnDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
- mDataTransferListeners.addAll(listeners);
+ @Override
+ public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.addAll(listeners);
+ }
}
- public void removeOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
- mDataTransferListeners.remove(listener);
+ @Override
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.remove(listener);
+ }
}
out.write(mBuffer.array(), 0, readCount);
mBuffer.clear();
mTransferred += readCount;
- it = mDataTransferListeners.iterator();
- while (it.hasNext()) {
- it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName());
+ synchronized (mDataTransferListeners) {
+ it = mDataTransferListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName());
+ }
}
}
import org.apache.commons.httpclient.methods.RequestEntity;
+import com.owncloud.android.network.ProgressiveDataTransferer;
+
import eu.alefzero.webdav.OnDatatransferProgressListener;
import android.util.Log;
* A RequestEntity that represents a File.
*
*/
-public class FileRequestEntity implements RequestEntity {
+public class FileRequestEntity implements RequestEntity, ProgressiveDataTransferer {
final File mFile;
final String mContentType;
public boolean isRepeatable() {
return true;
}
-
- public void addOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
- mDataTransferListeners.add(listener);
+
+ @Override
+ public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.add(listener);
+ }
}
- public void addOnDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
- mDataTransferListeners.addAll(listeners);
+ @Override
+ public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.addAll(listeners);
+ }
}
- public void removeOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
- mDataTransferListeners.remove(listener);
+ @Override
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+ synchronized (mDataTransferListeners) {
+ mDataTransferListeners.remove(listener);
+ }
}
out.write(tmp.array(), 0, readResult);
tmp.clear();
transferred += readResult;
- it = mDataTransferListeners.iterator();
- while (it.hasNext()) {
- it.next().onTransferProgress(readResult, transferred, size, mFile.getName());
+ synchronized (mDataTransferListeners) {
+ it = mDataTransferListeners.iterator();
+ while (it.hasNext()) {
+ it.next().onTransferProgress(readResult, transferred, size, mFile.getName());
+ }
}
}
try {\r
File f = new File(localFile);\r
FileRequestEntity entity = new FileRequestEntity(f, contentType);\r
- entity.addOnDatatransferProgressListener(mDataTransferListener);\r
+ entity.addDatatransferProgressListener(mDataTransferListener);\r
put.setRequestEntity(entity);\r
status = executeMethod(put);\r
\r