/*
* Copyright (C) 2016 Jorge Ruesga
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ruesga.android.wallpapers.photophase.providers;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import com.ruesga.android.wallpapers.photophase.AndroidHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TemporaryContentAccessProvider extends ContentProvider {
public static final String AUTHORITY = "com.ruesga.android.wallpapers.photophase.providers";
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY;
private static final String COLUMN_ID = "auth_id";
private static final String COLUMN_NAME = OpenableColumns.DISPLAY_NAME;
private static final String COLUMN_SIZE = OpenableColumns.SIZE;
private static final String[] COLUMN_PROJECTION = {
COLUMN_ID, COLUMN_NAME, COLUMN_SIZE
};
private static class AuthorizationResource {
private final File mFile;
private AuthorizationResource(Uri uri) {
mFile = new File(uri.getPath());
}
}
private static final Map<UUID, AuthorizationResource> AUTHORIZATIONS =
(Map<UUID, AuthorizationResource>) Collections.synchronizedMap(
new HashMap<UUID, AuthorizationResource>());
private static ScheduledThreadPoolExecutor sScheduler = new ScheduledThreadPoolExecutor(5);
public static Uri createAuthorizationUri(Uri uri) {
// Use this content provider only for Lollipop or greater
// TODO Just skip this check in case some day PhotoPhase will handle
// internal paths
if (!AndroidHelper.isLollipopOrGreater()) {
return uri;
}
// Generate a new authorization for the filesystem
UUID uuid;
do {
uuid = UUID.randomUUID();
if (!AUTHORIZATIONS.containsKey(uuid)) {
AuthorizationResource resource = new AuthorizationResource(uri);
AUTHORIZATIONS.put(uuid, resource);
break;
}
} while(true);
// Clean up authorization after 1 minute
final UUID token = uuid;
sScheduler.schedule(new Runnable() {
@Override
public void run() {
AUTHORIZATIONS.remove(token);
}
}, 1, TimeUnit.MINUTES);
// Return the authorization uri
return Uri.withAppendedPath(Uri.parse(CONTENT_AUTHORITY), uuid.toString());
}
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
AuthorizationResource authResource = getAuthorizationResourceForUri(uri);
if (authResource == null) {
throw new SecurityException("Authorization not exists");
}
// Create an in-memory cursor
String[] cols = new String[COLUMN_PROJECTION.length];
Object[] values = new Object[COLUMN_PROJECTION.length];
for (int i = 0; i < COLUMN_PROJECTION.length; i++) {
cols[i] = COLUMN_PROJECTION[i];
switch (i) {
case 0:
values[i] = uri.getLastPathSegment();
break;
case 1:
values[i] = authResource.mFile.getName();
break;
case 2:
values[i] = authResource.mFile.length();
break;
default:
break;
}
}
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
// Retrieve the authorization
AuthorizationResource authResource = getAuthorizationResourceForUri(uri);
if (authResource == null) {
throw new SecurityException("Authorization not exists");
}
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(authResource.mFile.getAbsolutePath()));
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
return this.openFile(uri, mode, null);
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
final CancellationSignal signal) throws FileNotFoundException {
final AuthorizationResource authResource = getAuthorizationResourceForUri(uri);
if (authResource == null) {
throw new SecurityException("Authorization not exists");
}
// Allocate the parcel descriptor
return ParcelFileDescriptor.open(authResource.mFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
@Nullable
private static AuthorizationResource getAuthorizationResourceForUri(Uri uri) {
try {
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(id)) {
return null;
}
UUID uuid = UUID.fromString(id);
if (uuid == null || !AUTHORIZATIONS.containsKey(uuid)) {
return null;
}
return AUTHORIZATIONS.get(uuid);
} catch (IllegalArgumentException ex) {
// Ignore
}
return null;
}
}