Browse Source

Merge pull request #3 from nextcloud/document_provider

Add documents provider
Bartosz Przybylski 8 years ago
parent
commit
7a54dc616a

+ 14 - 2
AndroidManifest.xml

@@ -129,17 +129,29 @@
             android:name=".providers.FileContentProvider"
             android:authorities="@string/authority"
             android:enabled="true"
-            android:exported="false"
+            android:exported="true"
             android:label="@string/sync_string_files"
             android:syncable="true" />
 
         <provider
             android:name=".providers.UsersAndGroupsSearchProvider"
-            android:authorities="com.nextcloud.android.providers.UsersAndGroupsSearchProvider"
+            android:authorities=".providers.UsersAndGroupsSearchProvider"
             android:enabled="true"
             android:exported="false"
             android:label="@string/search_users_and_groups_hint" />
 
+        <provider
+            android:authorities="@string/document_provider_authority"
+            android:name="org.nextcloud.providers.DocumentsStorageProvider"
+            android:exported="true"
+            android:grantUriPermissions="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+
         <activity
             android:name=".authentication.AuthenticatorActivity"
             android:exported="true"

+ 21 - 0
res/values-v19/bools.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  nextCloud Android client application
+
+  Copyright (C) 2016 Bartosz Przybylski <bart.p.pl@gmail.com>
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License version 2,
+  as published by the Free Software Foundation.
+
+  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/>.
+-->
+<resources>
+    <bool name="atLeastKitKat">true</bool>
+</resources>

+ 1 - 0
res/values/bools.xml

@@ -19,4 +19,5 @@
 <!-- Default boolean values -->
 <resources>
     <bool name="large_land_layout">false</bool>
+    <bool name="atLeastKitKat">false</bool>
 </resources>

+ 1 - 0
res/values/setup.xml

@@ -5,6 +5,7 @@
     <string name="account_type">nextcloud</string>	<!-- better if was a domain name; but changing it now would require
     migrate accounts when the app is updated -->
     <string name="authority">org.nextcloud</string>	<!-- better if was the app package with ".provider" appended ; it identifies the provider -->
+    <string name="document_provider_authority">org.nextcloud.documents</string>
     <string name ="db_file">nextcloud.db</string>
     <string name ="db_name">nextcloud</string>
     <string name ="data_folder">nextcloud</string>

+ 8 - 7
src/com/owncloud/android/authentication/AccountUtils.java

@@ -56,8 +56,7 @@ public class AccountUtils {
      *                      account). If none is available and valid, returns null.
      */
     public static Account getCurrentOwnCloudAccount(Context context) {
-        Account[] ocAccounts = AccountManager.get(context).getAccountsByType(
-                MainApp.getAccountType());
+        Account[] ocAccounts = getAccounts(context);
         Account defaultAccount = null;
 
         SharedPreferences appPreferences = PreferenceManager
@@ -83,10 +82,14 @@ public class AccountUtils {
         return defaultAccount;
     }
 
+    public static Account[] getAccounts(Context context) {
+        AccountManager accountManager = AccountManager.get(context);
+        return accountManager.getAccountsByType(MainApp.getAccountType());
+    }
+
     
     public static boolean exists(Account account, Context context) {
-        Account[] ocAccounts = AccountManager.get(context).getAccountsByType(
-                MainApp.getAccountType());
+        Account[] ocAccounts = getAccounts(context);
 
         if (account != null && account.name != null) {
             int lastAtPos = account.name.lastIndexOf("@");
@@ -128,10 +131,8 @@ public class AccountUtils {
     public static boolean setCurrentOwnCloudAccount(Context context, String accountName) {
         boolean result = false;
         if (accountName != null) {
-            Account[] ocAccounts = AccountManager.get(context).getAccountsByType(
-                    MainApp.getAccountType());
             boolean found;
-            for (Account account : ocAccounts) {
+            for (Account account : getAccounts(context)) {
                 found = (account.name.equals(accountName));
                 if (found) {
                     SharedPreferences.Editor appPrefs = PreferenceManager

+ 216 - 0
src/org/nextcloud/providers/DocumentsStorageProvider.java

@@ -0,0 +1,216 @@
+/**
+ *   nextCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2016  Bartosz Przybylski <bart.p.pl@gmail.com>
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 org.nextcloud.providers;
+
+import android.accounts.Account;
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsProvider;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import org.nextcloud.providers.cursors.FileCursor;
+import org.nextcloud.providers.cursors.RootCursor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class DocumentsStorageProvider extends DocumentsProvider {
+
+    private FileDataStorageManager mCurrentStorageManager = null;
+    private static Map<Long, FileDataStorageManager> mRootIdToStorageManager;
+
+    @Override
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        initiateStorageMap();
+
+        final RootCursor result = new RootCursor(projection);
+
+        for (Account account : AccountUtils.getAccounts(getContext()))
+            result.addRoot(account, getContext());
+
+        return result;
+    }
+
+    @Override
+    public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+        final long docId = Long.parseLong(documentId);
+        updateCurrentStorageManagerIfNeeded(docId);
+
+        final FileCursor result = new FileCursor(projection);
+        OCFile file = mCurrentStorageManager.getFileById(docId);
+        if (file != null)
+            result.addFile(file);
+
+        return result;
+    }
+
+    @Override
+    public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+
+        final long folderId = Long.parseLong(parentDocumentId);
+        updateCurrentStorageManagerIfNeeded(folderId);
+
+        final FileCursor result = new FileCursor(projection);
+
+        final OCFile browsedDir = mCurrentStorageManager.getFileById(folderId);
+        for (OCFile file : mCurrentStorageManager.getFolderContent(browsedDir)) {
+            result.addFile(file);
+        }
+
+        return result;
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal cancellationSignal)
+            throws FileNotFoundException {
+        final long docId = Long.parseLong(documentId);
+        updateCurrentStorageManagerIfNeeded(docId);
+
+        OCFile file = mCurrentStorageManager.getFileById(docId);
+
+        if (!file.isDown()) {
+
+            Intent i = new Intent(getContext(), FileDownloader.class);
+            i.putExtra(FileDownloader.EXTRA_ACCOUNT, mCurrentStorageManager.getAccount());
+            i.putExtra(FileDownloader.EXTRA_FILE, file);
+            getContext().startService(i);
+
+            do {
+                if (!waitOrGetCancelled(cancellationSignal)) {
+                    return null;
+                }
+                file = mCurrentStorageManager.getFileById(docId);
+
+            } while (!file.isDown());
+        }
+
+        return ParcelFileDescriptor.open(
+                new File(file.getStoragePath()), ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public AssetFileDescriptor openDocumentThumbnail(String documentId,
+                                                     Point sizeHint,
+                                                     CancellationSignal signal)
+            throws FileNotFoundException {
+        long docId = Long.parseLong(documentId);
+        updateCurrentStorageManagerIfNeeded(docId);
+
+        OCFile file = mCurrentStorageManager.getFileById(docId);
+
+        File realFile = new File(file.getStoragePath());
+
+        return new AssetFileDescriptor(
+                ParcelFileDescriptor.open(realFile, ParcelFileDescriptor.MODE_READ_ONLY),
+                0,
+                AssetFileDescriptor.UNKNOWN_LENGTH);
+    }
+
+    @Override
+    public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
+        updateCurrentStorageManagerIfNeeded(rootId);
+
+        OCFile root = mCurrentStorageManager.getFileByPath("/");
+        FileCursor result = new FileCursor(projection);
+
+        for (OCFile f : findFiles(root, query)) {
+            result.addFile(f);
+        }
+
+        return result;
+    }
+
+    private void updateCurrentStorageManagerIfNeeded(long docId) {
+        if (mCurrentStorageManager == null ||
+                (mRootIdToStorageManager.containsKey(docId) &&
+                        mCurrentStorageManager != mRootIdToStorageManager.get(docId))) {
+            mCurrentStorageManager = mRootIdToStorageManager.get(docId);
+        }
+    }
+
+    private void updateCurrentStorageManagerIfNeeded(String rootId) {
+        for (FileDataStorageManager data : mRootIdToStorageManager.values())
+            if (data.getAccount().name.equals(rootId)) {
+                mCurrentStorageManager = data;
+            }
+    }
+
+    private void initiateStorageMap() {
+
+        mRootIdToStorageManager = new HashMap<Long, FileDataStorageManager>();
+
+        ContentResolver contentResolver = getContext().getContentResolver();
+
+        for (Account account : AccountUtils.getAccounts(getContext())) {
+            final FileDataStorageManager storageManager =
+                    new FileDataStorageManager(account, contentResolver);
+            final OCFile rootDir = storageManager.getFileByPath("/");
+            mRootIdToStorageManager.put(rootDir.getFileId(), storageManager);
+        }
+
+    }
+
+    private boolean waitOrGetCancelled(CancellationSignal cancellationSignal) {
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            return false;
+        }
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    Vector<OCFile> findFiles(OCFile root, String query) {
+        Vector<OCFile> result = new Vector<OCFile>();
+        for (OCFile f : mCurrentStorageManager.getFolderContent(root)) {
+            if (f.isFolder()) {
+                result.addAll(findFiles(f, query));
+            } else if (f.getFileName().contains(query)) {
+                result.add(f);
+            }
+        }
+        return result;
+    }
+}

+ 60 - 0
src/org/nextcloud/providers/cursors/FileCursor.java

@@ -0,0 +1,60 @@
+/**
+ *   nextCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2016  Bartosz Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 org.nextcloud.providers.cursors;
+
+import android.annotation.TargetApi;
+import android.database.MatrixCursor;
+import android.os.Build;
+import android.provider.DocumentsContract.Document;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.utils.MimetypeIconUtil;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class FileCursor extends MatrixCursor {
+
+    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_MIME_TYPE, Document.COLUMN_SIZE,
+            Document.COLUMN_FLAGS, Document.COLUMN_LAST_MODIFIED
+    };
+
+    public FileCursor(String[] projection) {
+        super(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+    }
+
+    public void addFile(OCFile file) {
+        if (file == null) return;
+
+        final int iconRes = MimetypeIconUtil.getFileTypeIconId(file.getMimetype(), file.getFileName());
+        final String mimeType = file.isFolder() ? Document.MIME_TYPE_DIR : file.getMimetype();
+        final String imagePath = file.isImage() && file.isDown() ? file.getStoragePath() : null;
+        int flags = imagePath != null ? Document.FLAG_SUPPORTS_THUMBNAIL : 0;
+
+        newRow().add(Document.COLUMN_DOCUMENT_ID, Long.toString(file.getFileId()))
+                .add(Document.COLUMN_DISPLAY_NAME, file.getFileName())
+                .add(Document.COLUMN_LAST_MODIFIED, file.getModificationTimestamp())
+                .add(Document.COLUMN_SIZE, file.getFileLength())
+                .add(Document.COLUMN_FLAGS, flags)
+                .add(Document.COLUMN_ICON, iconRes)
+                .add(Document.COLUMN_MIME_TYPE, mimeType);
+    }
+}

+ 61 - 0
src/org/nextcloud/providers/cursors/RootCursor.java

@@ -0,0 +1,61 @@
+/**
+ *   nextCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2016  Bartosz Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 org.nextcloud.providers.cursors;
+
+import android.accounts.Account;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.os.Build;
+import android.provider.DocumentsContract.Root;
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class RootCursor extends MatrixCursor {
+
+    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_SUMMARY,
+            Root.COLUMN_FLAGS
+    };
+
+    public RootCursor(String[] projection) {
+        super(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
+    }
+
+    public void addRoot(Account account, Context context) {
+        final FileDataStorageManager manager =
+                new FileDataStorageManager(account, context.getContentResolver());
+        final OCFile mainDir = manager.getFileByPath("/");
+        newRow().add(Root.COLUMN_ROOT_ID, account.name)
+                .add(Root.COLUMN_DOCUMENT_ID, mainDir.getFileId())
+                .add(Root.COLUMN_SUMMARY, account.name)
+                .add(Root.COLUMN_TITLE, context.getString(R.string.app_name))
+                .add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
+                .add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_SEARCH);
+
+    }
+
+}