diff --git a/twidere/src/main/java/org/mariotaku/twidere/annotation/CacheFileType.java b/twidere/src/main/java/org/mariotaku/twidere/annotation/CacheFileType.java new file mode 100644 index 000000000..fc91aa7d0 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/annotation/CacheFileType.java @@ -0,0 +1,18 @@ +package org.mariotaku.twidere.annotation; + +import android.support.annotation.StringDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by mariotaku on 2017/2/4. + */ + +@StringDef({CacheFileType.IMAGE, CacheFileType.VIDEO, CacheFileType.JSON}) +@Retention(RetentionPolicy.SOURCE) +public @interface CacheFileType { + String IMAGE = "image"; + String VIDEO = "video"; + String JSON = "json"; +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/CacheProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/CacheProvider.java deleted file mode 100644 index 4a8011de9..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/CacheProvider.java +++ /dev/null @@ -1,239 +0,0 @@ -package org.mariotaku.twidere.provider; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringDef; -import android.webkit.MimeTypeMap; - -import com.bluelinelabs.logansquare.JsonMapper; -import com.nostra13.universalimageloader.cache.disc.DiskCache; - -import org.mariotaku.commons.logansquare.LoganSquareMapperFinder; -import org.mariotaku.restfu.RestFuUtils; -import org.mariotaku.twidere.TwidereConstants; -import org.mariotaku.twidere.model.CacheMetadata; -import org.mariotaku.twidere.task.SaveFileTask; -import org.mariotaku.twidere.util.BitmapUtils; -import org.mariotaku.twidere.util.dagger.GeneralComponentHelper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Locale; - -import javax.inject.Inject; - -import okio.ByteString; - -/** - * Created by mariotaku on 16/1/1. - */ -public class CacheProvider extends ContentProvider implements TwidereConstants { - @Inject - DiskCache mSimpleDiskCache; - - public static Uri getCacheUri(String key, @Type String type) { - final Uri.Builder builder = new Uri.Builder(); - builder.scheme(ContentResolver.SCHEME_CONTENT); - builder.authority(TwidereConstants.AUTHORITY_TWIDERE_CACHE); - builder.appendPath(ByteString.encodeUtf8(key).base64Url()); - if (type != null) { - builder.appendQueryParameter(QUERY_PARAM_TYPE, type); - } - return builder.build(); - } - - @NonNull - public static String getCacheKey(Uri uri) { - if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) - throw new IllegalArgumentException(uri.toString()); - if (!TwidereConstants.AUTHORITY_TWIDERE_CACHE.equals(uri.getAuthority())) - throw new IllegalArgumentException(uri.toString()); - return ByteString.decodeBase64(uri.getLastPathSegment()).utf8(); - } - - - public static String getMetadataKey(Uri uri) { - if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) - throw new IllegalArgumentException(uri.toString()); - if (!TwidereConstants.AUTHORITY_TWIDERE_CACHE.equals(uri.getAuthority())) - throw new IllegalArgumentException(uri.toString()); - return getExtraKey(ByteString.decodeBase64(uri.getLastPathSegment()).utf8()); - } - - public static String getExtraKey(String key) { - return key + ".extra"; - } - - @Override - public boolean onCreate() { - final Context context = getContext(); - assert context != null; - GeneralComponentHelper.build(context).inject(this); - return true; - } - - @Nullable - @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return null; - } - - @Nullable - @Override - public String getType(@NonNull Uri uri) { - final CacheMetadata metadata = getMetadata(uri); - if (metadata != null) { - return metadata.getContentType(); - } - final String type = uri.getQueryParameter(QUERY_PARAM_TYPE); - if (type != null) { - switch (type) { - case Type.IMAGE: { - final File file = mSimpleDiskCache.get(getCacheKey(uri)); - if (file == null) return null; - return BitmapUtils.getImageMimeType(file); - } - case Type.VIDEO: { - return "video/mp4"; - } - case Type.JSON: { - return "application/json"; - } - } - } - return null; - } - - public CacheMetadata getMetadata(@NonNull Uri uri) { - final File file = mSimpleDiskCache.get(getMetadataKey(uri)); - if (file == null) return null; - FileInputStream is = null; - try { - final JsonMapper mapper = LoganSquareMapperFinder.mapperFor(CacheMetadata.class); - is = new FileInputStream(file); - return mapper.parse(is); - } catch (IOException e) { - return null; - } finally { - RestFuUtils.closeSilently(is); - } - } - - @Nullable - @Override - public Uri insert(@NonNull Uri uri, ContentValues values) { - return null; - } - - @Override - public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { - return 0; - } - - @Override - public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return 0; - } - - @Nullable - @Override - public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { - try { - final File file = mSimpleDiskCache.get(getCacheKey(uri)); - if (file == null) throw new FileNotFoundException(); - final int modeBits = modeToMode(mode); - if (modeBits != ParcelFileDescriptor.MODE_READ_ONLY) - throw new IllegalArgumentException("Cache can't be opened for write"); - return ParcelFileDescriptor.open(file, modeBits); - } catch (IOException e) { - throw new FileNotFoundException(); - } - } - - /** - * Copied from ContentResolver.java - */ - private static int modeToMode(String mode) { - int modeBits; - if ("r".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_ONLY; - } else if ("w".equals(mode) || "wt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else if ("wa".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_APPEND; - } else if ("rw".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE; - } else if ("rwt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else { - throw new IllegalArgumentException("Invalid mode: " + mode); - } - return modeBits; - } - - public static final class CacheFileTypeCallback implements SaveFileTask.FileInfoCallback { - private final Context context; - private final String type; - - public CacheFileTypeCallback(Context context, @Type String type) { - this.context = context; - this.type = type; - } - - @Override - @NonNull - public String getFilename(@NonNull Uri source) { - String cacheKey = getCacheKey(source); - final int indexOfSsp = cacheKey.indexOf("://"); - if (indexOfSsp != -1) { - cacheKey = cacheKey.substring(indexOfSsp + 3); - } - return cacheKey.replaceAll("[^\\w\\d_]", String.valueOf(getSpecialCharacter())); - } - - @Override - @Nullable - public String getMimeType(@NonNull Uri source) { - if (type == null || source.getQueryParameter(QUERY_PARAM_TYPE) != null) { - return context.getContentResolver().getType(source); - } - final Uri.Builder builder = source.buildUpon(); - builder.appendQueryParameter(QUERY_PARAM_TYPE, type); - return context.getContentResolver().getType(builder.build()); - } - - @Override - public String getExtension(@Nullable String mimeType) { - if (mimeType == null) return null; - return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType.toLowerCase(Locale.US)); - } - - @Override - public char getSpecialCharacter() { - return '_'; - } - } - - @StringDef({Type.IMAGE, Type.VIDEO, Type.JSON}) - public @interface Type { - String IMAGE = "image"; - String VIDEO = "video"; - String JSON = "json"; - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 97949c443..4c06abfad 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -293,6 +293,15 @@ public final class Utils implements Constants { } } + public static String sanitizeMimeType(@Nullable final String contentType) { + if (contentType == null) return null; + switch (contentType) { + case "image/jpg": + return "image/jpeg"; + } + return contentType; + } + public static class NoAccountException extends Exception { String accountHost; diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/media/TwidereMediaDownloader.java b/twidere/src/main/java/org/mariotaku/twidere/util/media/TwidereMediaDownloader.java index 1c1d4a612..7dd6a435c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/media/TwidereMediaDownloader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/media/TwidereMediaDownloader.java @@ -36,6 +36,7 @@ import org.mariotaku.twidere.util.JsonSerializer; import org.mariotaku.twidere.util.MicroBlogAPIFactory; import org.mariotaku.twidere.util.SharedPreferencesWrapper; import org.mariotaku.twidere.util.UserAgentUtils; +import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor; import org.mariotaku.twidere.util.net.NoIntercept; @@ -176,7 +177,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants { } final Body body = resp.getBody(); final CacheMetadata metadata = new CacheMetadata(); - metadata.setContentType(body.contentType().getContentType()); + metadata.setContentType(Utils.sanitizeMimeType(body.contentType().getContentType())); return new TwidereDownloadResult(body, metadata); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/media/UILFileCache.java b/twidere/src/main/java/org/mariotaku/twidere/util/media/UILFileCache.java index c2a35d10c..f227ba2cb 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/media/UILFileCache.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/media/UILFileCache.java @@ -45,19 +45,19 @@ public class UILFileCache implements FileCache { } }); if (extra != null) { - cache.save(CacheProvider.getExtraKey(key), new ByteArrayInputStream(extra), null); + cache.save(CacheProvider.Companion.getExtraKey(key), new ByteArrayInputStream(extra), null); } } @NonNull @Override public Uri toUri(@NonNull final String key) { - return CacheProvider.getCacheUri(key, null); + return CacheProvider.Companion.getCacheUri(key, null); } @NonNull @Override public String fromUri(@NonNull final Uri uri) { - return CacheProvider.getCacheKey(uri); + return CacheProvider.Companion.getCacheKey(uri); } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt index f633df616..cc458cfdc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt @@ -48,6 +48,7 @@ import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper import org.mariotaku.twidere.activity.iface.IExtendedActivity +import org.mariotaku.twidere.annotation.CacheFileType import org.mariotaku.twidere.fragment.* import org.mariotaku.twidere.fragment.iface.IBaseFragment import org.mariotaku.twidere.model.ParcelableMedia @@ -428,9 +429,9 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos val destination = ShareProvider.getFilesDir(this) ?: return val type: String when (f) { - is VideoPageFragment -> type = CacheProvider.Type.VIDEO - is ImagePageFragment -> type = CacheProvider.Type.IMAGE - is GifPageFragment -> type = CacheProvider.Type.IMAGE + is VideoPageFragment -> type = CacheFileType.VIDEO + is ImagePageFragment -> type = CacheFileType.IMAGE + is GifPageFragment -> type = CacheFileType.IMAGE else -> throw UnsupportedOperationException("Unsupported fragment $f") } val task = object : SaveFileTask(this@MediaViewerActivity, cacheUri, destination, @@ -488,9 +489,9 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? CacheDownloadMediaViewerFragment ?: return val cacheUri = f.downloadResult?.cacheUri ?: return val task: SaveFileTask = when (f) { - is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE) - is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.VIDEO) - is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE) + is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE) + is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.VIDEO) + is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE) else -> throw UnsupportedOperationException() } AsyncTaskUtils.executeTask(task) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/WebLinkHandlerActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/WebLinkHandlerActivity.kt index e5d199c37..0f235f33a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/WebLinkHandlerActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/WebLinkHandlerActivity.kt @@ -93,16 +93,15 @@ class WebLinkHandlerActivity : Activity() { return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true) } else -> { - if (!ArrayUtils.contains(FANFOU_RESERVED_PATHS, pathSegments[0])) { - if (pathSegments.size == 1) { - val builder = Uri.Builder() - builder.scheme(SCHEME_TWIDERE) - builder.authority(AUTHORITY_USER) - builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM) - val userKey = UserKey(pathSegments[0], USER_TYPE_FANFOU_COM) - builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString()) - return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true) - } + if (FANFOU_RESERVED_PATHS.contains(pathSegments[0])) return Pair(null, false) + if (pathSegments.size == 1) { + val builder = Uri.Builder() + builder.scheme(SCHEME_TWIDERE) + builder.authority(AUTHORITY_USER) + builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM) + val userKey = UserKey(pathSegments[0], USER_TYPE_FANFOU_COM) + builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString()) + return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true) } return Pair(null, false) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt index 97a67ce76..7daa6e583 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt @@ -26,6 +26,7 @@ import android.support.v4.content.AsyncTaskLoader import org.mariotaku.ktextension.set import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.twitter.model.ErrorInfo +import org.mariotaku.restfu.http.RestHttpClient import org.mariotaku.twidere.constant.IntentConstants import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT import org.mariotaku.twidere.model.ParcelableStatus @@ -51,7 +52,9 @@ class ParcelableStatusLoader( ) : AsyncTaskLoader>(context) { @Inject - lateinit var userColorNameManager: UserColorNameManager + internal lateinit var userColorNameManager: UserColorNameManager + @Inject + internal lateinit var restHttpClient: RestHttpClient init { GeneralComponentHelper.build(context).inject(this) @@ -81,8 +84,7 @@ class ParcelableStatusLoader( if (e.errorCode == ErrorInfo.STATUS_NOT_FOUND) { // Delete all deleted status val cr = context.contentResolver - DataStoreUtils.deleteStatus(cr, accountKey, - statusId, null) + DataStoreUtils.deleteStatus(cr, accountKey, statusId, null) DataStoreUtils.deleteActivityStatus(cr, accountKey, statusId, null) } return SingleResponse(e) @@ -94,4 +96,5 @@ class ParcelableStatusLoader( forceLoad() } + } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/provider/CacheProvider.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/CacheProvider.kt new file mode 100644 index 000000000..3e937c385 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/CacheProvider.kt @@ -0,0 +1,194 @@ +package org.mariotaku.twidere.provider + +import android.content.ContentProvider +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.os.ParcelFileDescriptor +import android.webkit.MimeTypeMap +import com.nostra13.universalimageloader.cache.disc.DiskCache +import okio.ByteString +import org.mariotaku.commons.logansquare.LoganSquareMapperFinder +import org.mariotaku.restfu.RestFuUtils +import org.mariotaku.twidere.TwidereConstants +import org.mariotaku.twidere.annotation.CacheFileType +import org.mariotaku.twidere.model.CacheMetadata +import org.mariotaku.twidere.task.SaveFileTask +import org.mariotaku.twidere.util.BitmapUtils +import org.mariotaku.twidere.util.dagger.GeneralComponentHelper +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.util.* +import javax.inject.Inject + +/** + * Created by mariotaku on 16/1/1. + */ +class CacheProvider : ContentProvider() { + @Inject + internal lateinit var simpleDiskCache: DiskCache + + override fun onCreate(): Boolean { + GeneralComponentHelper.build(context).inject(this) + return true + } + + override fun query(uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String?): Cursor? { + return null + } + + override fun getType(uri: Uri): String? { + val metadata = getMetadata(uri) + if (metadata != null) { + return metadata.contentType + } + val type = uri.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE) + when (type) { + CacheFileType.IMAGE -> { + val file = simpleDiskCache.get(getCacheKey(uri)) ?: return null + return BitmapUtils.getImageMimeType(file) + } + CacheFileType.VIDEO -> { + return "video/mp4" + } + CacheFileType.JSON -> { + return "application/json" + } + } + return null + } + + fun getMetadata(uri: Uri): CacheMetadata? { + val file = simpleDiskCache.get(getMetadataKey(uri)) ?: return null + var `is`: FileInputStream? = null + try { + val mapper = LoganSquareMapperFinder.mapperFor(CacheMetadata::class.java) + `is` = FileInputStream(file) + return mapper.parse(`is`) + } catch (e: IOException) { + return null + } finally { + RestFuUtils.closeSilently(`is`) + } + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + return null + } + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + return 0 + } + + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { + return 0 + } + + @Throws(FileNotFoundException::class) + override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { + try { + val file = simpleDiskCache.get(getCacheKey(uri)) ?: throw FileNotFoundException() + val modeBits = modeToMode(mode) + if (modeBits != ParcelFileDescriptor.MODE_READ_ONLY) + throw IllegalArgumentException("Cache can't be opened for write") + return ParcelFileDescriptor.open(file, modeBits) + } catch (e: IOException) { + throw FileNotFoundException() + } + + } + + class CacheFileTypeCallback(private val context: Context, @CacheFileType private val type: String?) : SaveFileTask.FileInfoCallback { + + override fun getFilename(source: Uri): String { + var cacheKey = getCacheKey(source) + val indexOfSsp = cacheKey.indexOf("://") + if (indexOfSsp != -1) { + cacheKey = cacheKey.substring(indexOfSsp + 3) + } + return cacheKey.replace("[^\\w\\d_]".toRegex(), specialCharacter.toString()) + } + + override fun getMimeType(source: Uri): String? { + if (type == null || source.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE) != null) { + return context.contentResolver.getType(source) + } + val builder = source.buildUpon() + builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type) + return context.contentResolver.getType(builder.build()) + } + + override fun getExtension(mimeType: String?): String? { + val typeLowered = mimeType?.toLowerCase(Locale.US) ?: return null + return when (typeLowered) { + // Hack for fanfou image type + "image/jpg" -> "jpg" + else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(typeLowered) + } + } + + override val specialCharacter: Char + get() = '_' + } + + + companion object { + + fun getCacheUri(key: String, @CacheFileType type: String?): Uri { + val builder = Uri.Builder() + builder.scheme(ContentResolver.SCHEME_CONTENT) + builder.authority(TwidereConstants.AUTHORITY_TWIDERE_CACHE) + builder.appendPath(ByteString.encodeUtf8(key).base64Url()) + if (type != null) { + builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type) + } + return builder.build() + } + + fun getCacheKey(uri: Uri): String { + if (ContentResolver.SCHEME_CONTENT != uri.scheme) + throw IllegalArgumentException(uri.toString()) + if (TwidereConstants.AUTHORITY_TWIDERE_CACHE != uri.authority) + throw IllegalArgumentException(uri.toString()) + return ByteString.decodeBase64(uri.lastPathSegment).utf8() + } + + + fun getMetadataKey(uri: Uri): String { + if (ContentResolver.SCHEME_CONTENT != uri.scheme) + throw IllegalArgumentException(uri.toString()) + if (TwidereConstants.AUTHORITY_TWIDERE_CACHE != uri.authority) + throw IllegalArgumentException(uri.toString()) + return getExtraKey(ByteString.decodeBase64(uri.lastPathSegment).utf8()) + } + + fun getExtraKey(key: String): String { + return key + ".extra" + } + + /** + * Copied from ContentResolver.java + */ + private fun modeToMode(mode: String): Int { + val modeBits: Int + if ("r" == mode) { + modeBits = ParcelFileDescriptor.MODE_READ_ONLY + } else if ("w" == mode || "wt" == mode) { + modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE + } else if ("wa" == mode) { + modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_APPEND + } else if ("rw" == mode) { + modeBits = ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE + } else if ("rwt" == mode) { + modeBits = ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE + } else { + throw IllegalArgumentException("Invalid mode: " + mode) + } + return modeBits + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveFileTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveFileTask.kt index 1b5e1c67f..e2d0298d7 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveFileTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveFileTask.kt @@ -33,8 +33,12 @@ import java.io.File import java.io.IOException import java.lang.ref.WeakReference -abstract class SaveFileTask(context: Context, private val source: Uri, - private val destination: File, private val getMimeType: SaveFileTask.FileInfoCallback) : AsyncTask() { +abstract class SaveFileTask( + context: Context, + private val source: Uri, + private val destination: File, + private val getMimeType: SaveFileTask.FileInfoCallback +) : AsyncTask() { private val contextRef: WeakReference @@ -44,7 +48,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri, override fun doInBackground(vararg args: Any): SaveFileResult? { val context = contextRef.get() ?: return null - return saveFile(context, source, getMimeType, destination) + return saveFile(context, source, getMimeType, destination, requiresValidExtension) } override fun onCancelled() { @@ -72,6 +76,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri, protected abstract fun dismissProgress() + open val requiresValidExtension: Boolean = false protected val context: Context? get() = contextRef.get() @@ -102,7 +107,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri, fun saveFile(context: Context, source: Uri, fileInfoCallback: FileInfoCallback, - destinationDir: File): SaveFileResult? { + destinationDir: File, requiresValidExtension: Boolean): SaveFileResult? { val cr = context.contentResolver var ioSrc: Source? = null var sink: BufferedSink? = null @@ -114,6 +119,9 @@ abstract class SaveFileTask(context: Context, private val source: Uri, } val mimeType = fileInfoCallback.getMimeType(source) ?: return null val extension = fileInfoCallback.getExtension(mimeType) + if (requiresValidExtension && extension == null) { + return null + } if (!destinationDir.isDirectory && !destinationDir.mkdirs()) return null var nameToSave = getFileNameWithExtension(name, extension, fileInfoCallback.specialCharacter, null) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt index 62872b462..d7a2e00c1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt @@ -25,6 +25,7 @@ import android.net.Uri import android.os.Environment import android.widget.Toast import org.mariotaku.twidere.R +import org.mariotaku.twidere.annotation.CacheFileType import org.mariotaku.twidere.provider.CacheProvider import java.io.File @@ -52,14 +53,13 @@ class SaveMediaToGalleryTask( companion object { - fun create(activity: Activity, source: Uri, - @CacheProvider.Type type: String): SaveFileTask { + fun create(activity: Activity, source: Uri, @CacheFileType type: String): SaveFileTask { val pubDir: File when (type) { - CacheProvider.Type.VIDEO -> { + CacheFileType.VIDEO -> { pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) } - CacheProvider.Type.IMAGE -> { + CacheFileType.IMAGE -> { pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) } else -> { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/OnLinkClickHandler.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/OnLinkClickHandler.kt index 90ee23be3..653dd2f9d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/OnLinkClickHandler.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/OnLinkClickHandler.kt @@ -42,6 +42,7 @@ import org.mariotaku.twidere.constant.newDocumentApiKey import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.util.ParcelableMediaUtils import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener +import org.mariotaku.twidere.util.TwidereLinkify.USER_TYPE_FANFOU_COM import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor open class OnLinkClickHandler( @@ -72,16 +73,16 @@ open class OnLinkClickHandler( return true } TwidereLinkify.LINK_TYPE_LINK_IN_TEXT -> { - if (isMedia(link, extraId)) { - openMedia(accountKey!!, extraId, sensitive, link, start, end) + if (accountKey != null && isMedia(link, extraId)) { + openMedia(accountKey, extraId, sensitive, link, start, end) } else { openLink(link) } return true } TwidereLinkify.LINK_TYPE_ENTITY_URL -> { - if (isMedia(link, extraId)) { - openMedia(accountKey!!, extraId, sensitive, link, start, end) + if (accountKey != null && isMedia(link, extraId)) { + openMedia(accountKey, extraId, sensitive, link, start, end) } else { val authority = UriUtils.getAuthority(link) if (authority == null) { @@ -89,36 +90,12 @@ open class OnLinkClickHandler( return true } when (authority) { - "fanfou.com" -> { - if (orig != null) { - // Process special case for fanfou - val ch = orig[0] - // Extend selection - val length = orig.length - if (TwidereLinkify.isAtSymbol(ch)) { - var id = UriUtils.getPath(link) - if (id != null) { - val idxOfSlash = id.indexOf('/') - if (idxOfSlash == 0) { - id = id.substring(1) - } - val screenName = orig.substring(1, length) - IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id), - screenName, preferences[newDocumentApiKey], Referral.USER_MENTION, - null) - return true - } - } else if (TwidereLinkify.isHashSymbol(ch) && TwidereLinkify.isHashSymbol(orig[length - 1])) { - IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1)) - return true - } - } + "fanfou.com" -> if (accountKey != null && handleFanfouLink(link, orig, accountKey)) { + return true } - else -> { - if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) { - openTwitterLink(link, accountKey!!) - return true - } + else -> if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) { + openTwitterLink(link, accountKey!!) + return true } } openLink(link) @@ -203,4 +180,36 @@ open class OnLinkClickHandler( } } + + private fun handleFanfouLink(link: String, orig: String?, accountKey: UserKey): Boolean { + if (orig == null) return false + // Process special case for fanfou + val ch = orig[0] + // Extend selection + val length = orig.length + if (TwidereLinkify.isAtSymbol(ch)) { + var id = UriUtils.getPath(link) + if (id != null) { + val idxOfSlash = id.indexOf('/') + if (idxOfSlash == 0) { + id = id.substring(1) + } + val screenName = orig.substring(1, length) + IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id), + screenName, preferences[newDocumentApiKey], Referral.USER_MENTION, + null) + return true + } + } else if (TwidereLinkify.isHashSymbol(ch) && TwidereLinkify.isHashSymbol(orig[length - 1])) { + IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1)) + return true + } + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) + intent.setClass(context, WebLinkHandlerActivity::class.java) + if (accountKey.host == USER_TYPE_FANFOU_COM) { + intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey) + } + context.startActivity(intent) + return true + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/NameView.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/NameView.kt index 33da87020..f907a5d98 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/NameView.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/NameView.kt @@ -42,9 +42,6 @@ class NameView(context: Context, attrs: AttributeSet? = null) : AppCompatTextVie var nameFirst: Boolean = false var twoLine: Boolean = false - get() { - return maxLines >= 2 - } set(value) { field = value if (value) { diff --git a/twidere/src/main/res/values/strings.xml b/twidere/src/main/res/values/strings.xml index 21281993f..c379273e3 100644 --- a/twidere/src/main/res/values/strings.xml +++ b/twidere/src/main/res/values/strings.xml @@ -287,7 +287,7 @@ Cards Clear cache - Clear stored profile image cache. + Clear stored media cache. Clear databases Clear all tweets, profiles, messages. Your account credentials won\'t be lost. Clear search history