Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this need a permission Api implementation for marshmallow and up?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the reason is that this provider is exported so user are specifically calling for document provider which shouldn't require any android requirements (it queries only app local resources).
But I cannot check that on a real device because of lack of android 6 device :(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I finally need to acquire one ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have Android N Preview 3 on my phone so I can install the branch in the next days. Afk right now...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like I commented, tested on Android N Preview, works like a charm 👍

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"
Expand Down
21 changes: 21 additions & 0 deletions res/values-v19/bools.xml
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions res/values/bools.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
<!-- Default boolean values -->
<resources>
<bool name="large_land_layout">false</bool>
<bool name="atLeastKitKat">false</bool>
</resources>
1 change: 1 addition & 0 deletions res/values/setup.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
15 changes: 8 additions & 7 deletions src/com/owncloud/android/authentication/AccountUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -83,10 +82,14 @@ public static Account getCurrentOwnCloudAccount(Context context) {
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("@");
Expand Down Expand Up @@ -128,10 +131,8 @@ public static Account getOwnCloudAccountByName(Context context, String accountNa
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
Expand Down
216 changes: 216 additions & 0 deletions src/org/nextcloud/providers/DocumentsStorageProvider.java
Original file line number Diff line number Diff line change
@@ -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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here ;)

}

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question. Is wrapping in {} a part of coding standard?

}
}

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 changes: 60 additions & 0 deletions src/org/nextcloud/providers/cursors/FileCursor.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading