Feat!: Switch to Compose Multiplatform

This commit is contained in:
Hai Zhang 2024-05-18 15:57:34 -07:00
parent c44710e777
commit 2153e6d9d1
1119 changed files with 5025 additions and 93784 deletions

6
.gitignore vendored
View File

@ -1,9 +1,9 @@
/.gradle/
/.idea/
/.kotlin/
/build/
/captures/
/local.properties
/signing.jks
/signing.properties
/*/.gradle/
/*/build/
.DS_Store
*.iml

4
app/.gitignore vendored
View File

@ -1,4 +0,0 @@
/.cxx/
/.externalNativeBuild/
/build/
/out/

View File

@ -1,21 +0,0 @@
cmake_minimum_required(VERSION 3.6)
project(MaterialFiles C)
add_library(hiddenapi SHARED
src/main/jni/hiddenapi.c)
target_compile_options(hiddenapi
PRIVATE
-Wall
-Werror)
find_library(LOG_LIBRARY log)
add_library(syscall SHARED
src/main/jni/syscall.c)
target_compile_options(syscall
PRIVATE
-Wall
-Werror)
target_link_libraries(syscall
PRIVATE
${LOG_LIBRARY})

View File

@ -1,218 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply from: '../signing.gradle'
//#ifdef NONFREE
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.1'
}
}
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
//#endif
android {
namespace 'me.zhanghai.android.files'
buildToolsVersion = '34.0.0'
compileSdk 34
ndkVersion '26.3.11579264'
defaultConfig {
applicationId 'me.zhanghai.android.files'
minSdk 21
// Not supporting notification runtime permission yet.
//noinspection OldTargetApi
targetSdk 34
versionCode 37
versionName '1.7.2'
resValue 'string', 'app_version', versionName + ' (' + versionCode + ')'
buildConfigField 'String', 'FILE_PROVIDIER_AUTHORITY', 'APPLICATION_ID + ".file_provider"'
resValue 'string', 'app_provider_authority', applicationId + '.app_provider'
resValue 'string', 'file_provider_authority', applicationId + '.file_provider'
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=none'
}
}
}
buildFeatures {
aidl true
buildConfig true
viewBinding true
}
androidResources {
generateLocaleConfig true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
lint {
// For "Invalid package reference in library; not included in Android: javax.security.sasl.
// Referenced from org.apache.mina.proxy.ProxyAuthException."
warning 'InvalidPackage', 'MissingTranslation'
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
//#ifdef NONFREE
firebaseCrashlytics {
nativeSymbolUploadEnabled true
}
//#endif
}
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
resources {
excludes += [
'META-INF/DEPENDENCIES',
'org/bouncycastle/pqc/crypto/picnic/*'
]
}
}
bundle {
language {
enableSplit = false
}
}
}
repositories {
maven {
url 'https://jitpack.io'
}
}
dependencies {
implementation('com.github.zhanghai:dav4jvm:c317607') {
exclude group: 'org.ogce', module: 'xpp3'
}
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.github.topjohnwu.libsu:service:5.2.2'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// kotlinx-coroutines-android depends on kotlin-stdlib-jdk8
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlinx_coroutines_version = '1.8.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
implementation 'androidx.activity:activity-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.drawerlayout:drawerlayout:1.2.0'
implementation 'androidx.exifinterface:exifinterface:1.3.7'
implementation 'androidx.fragment:fragment-ktx:1.7.1'
def androidx_lifecycle_version = '2.8.1'
implementation "androidx.lifecycle:lifecycle-common-java8:$androidx_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidx_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$androidx_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$androidx_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_lifecycle_version"
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.h6ah4i.android.materialshadowninepatch:materialshadowninepatch:1.0.0'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
// SMBJ 0.12.0 breaks anonymous authentication: https://github.com/hierynomus/smbj/issues/792
//noinspection GradleDependency
implementation ('com.hierynomus:smbj:0.11.5') {
// org.bouncycastle:bcprov-jdk15on uses bytecode version unsupported by Jetifier, so use
// org.bouncycastle:bcprov-jdk15to18 instead.
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
}
// SSHJ 0.36.0 requires Java 8.
//noinspection GradleDependency
implementation ('com.hierynomus:sshj:0.35.0') {
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
}
implementation 'com.jakewharton.threetenabp:threetenabp:1.4.6'
implementation 'com.leinardi.android:speed-dial:3.3.0'
implementation ('com.rapid7.client:dcerpc:0.12.1') {
// SMBJ-RPC depends on the JRE flavor of Guava which targets Java 8.
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
}
implementation 'com.google.guava:guava:33.0.0-android'
// Guava conflicts with com.google.guava:listenablefuture:1.0 pulled in by AndroidX Core
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
// Commons Net 3.9.0 started using java.time.Duration in FTPClient.
//noinspection GradleDependency
implementation 'commons-net:commons-net:3.8.0'
// LicensesDialog 2.2.0 pulls in androidx.webkit and uses setForceDark() instead of correctly
// setting colors.
//noinspection GradleDependency
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
// dev.chrisbanesinsetter:insetter:0.6.0 makes inset unstable when entering immersive.
implementation 'dev.chrisbanes:insetter-ktx:0.3.1'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.shizuku:api:13.1.5'
implementation ('eu.agno3.jcifs:jcifs-ng:2.1.10') {
exclude group: 'org.bouncycastle', module: 'bcprov-jdk18on'
}
implementation 'org.bouncycastle:bcprov-jdk15to18:1.77'
implementation platform('io.coil-kt:coil-bom:2.6.0')
implementation 'io.coil-kt:coil'
implementation 'io.coil-kt:coil-gif'
implementation 'io.coil-kt:coil-svg'
implementation 'io.coil-kt:coil-video'
implementation 'me.zhanghai.android.appiconloader:appiconloader:1.5.0'
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'me.zhanghai.android.foregroundcompat:library:1.0.2'
implementation 'me.zhanghai.android.libarchive:library:1.0.3'
implementation 'me.zhanghai.android.libselinux:library:2.1.0'
implementation 'me.zhanghai.android.retrofile:library:1.1.1'
implementation 'me.zhanghai.android.systemuihelper:library:1.0.0'
implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.4'
implementation 'org.apache.ftpserver:ftpserver-core:1.2.0'
// This is a dependency of org.apache.ftpserver:ftpserver-core but org.apache.mina:mina-core
// 2.1.3+ became incompatible before API 24 due to dependency on StandardSocketOptions
// (DIRMINA-1123) and NetworkChannel.supportedOptions() (DIRMINA-1130).
implementation ('org.apache.mina:mina-core') {
version {
strictly '2.1.3'
}
}
// Also a dependency of jCIFS-NG.
implementation 'org.slf4j:slf4j-android:1.7.36'
//#ifdef NONFREE
implementation platform('com.google.firebase:firebase-bom:33.1.0')
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-crashlytics-ndk'
//#endif
}

View File

@ -1,40 +0,0 @@
{
"project_info": {
"project_number": "728501351509",
"firebase_url": "https://zhanghai-materialfiles.firebaseio.com",
"project_id": "zhanghai-materialfiles",
"storage_bucket": "zhanghai-materialfiles.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:728501351509:android:007cb5eb181702a9",
"android_client_info": {
"package_name": "me.zhanghai.android.files"
}
},
"oauth_client": [
{
"client_id": "728501351509-3ul5c5u8jpd9q9gi4e7537bd31r5nkr5.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDUcQAOKRcCjsA-e5-cZHbt4HQQFDP4wA4"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "728501351509-3ul5c5u8jpd9q9gi4e7537bd31r5nkr5.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@ -1,57 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Native methods
# https://www.guardsquare.com/en/products/proguard/manual/examples#native
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# App
-keep class me.zhanghai.android.files.** implements androidx.appcompat.view.CollapsibleActionView { *; }
-keep class me.zhanghai.android.files.provider.common.ByteString { *; }
-keep class me.zhanghai.android.files.provider.linux.syscall.** { *; }
-keepnames class * extends java.lang.Exception
# For Class.getEnumConstants()
-keepclassmembers enum * {
public static **[] values();
}
-keepnames class me.zhanghai.android.files.** implements android.os.Parcelable
# Apache FtpServer
-keepclassmembers class * implements org.apache.mina.core.service.IoProcessor {
public <init>(java.util.concurrent.ExecutorService);
public <init>(java.util.concurrent.Executor);
public <init>();
}
# Bouncy Castle
-keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; }
# SMBJ
-dontwarn javax.el.**
-dontwarn org.ietf.jgss.**
-dontwarn sun.security.x509.X509Key
# SMBJ-RPC
-dontwarn java.rmi.UnmarshalException

View File

@ -1,399 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
~ All Rights Reserved.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- Shizuku requires API 23. -->
<uses-sdk tools:overrideLibrary="rikka.shizuku.aidl,rikka.shizuku.api,rikka.shizuku.shared" />
<!--
~ Samsung DeX requires explicitly setting android:resizeableActivity="true" for the app to be
~ resizable.
~ TODO: Remove this attribute once Samsung respects the default value for it.
-->
<application
android:allowBackup="true"
android:banner="@drawable/banner"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="true"
android:icon="@mipmap/launcher_icon"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/netework_security_config"
android:requestLegacyExternalStorage="true"
android:requestRawExternalStorageAccess="true"
android:resizeableActivity="true"
android:roundIcon="@mipmap/launcher_icon"
android:supportsRtl="true"
android:theme="@style/Theme.MaterialFiles"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<activity
android:name="me.zhanghai.android.files.filelist.FileListActivity"
android:exported="true"
android:visibleToInstantApps="true"
tools:ignore="UnusedAttribute">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="inode/directory" />
<data android:mimeType="resource/folder" />
<data android:mimeType="vnd.android.document/directory" />
</intent-filter>
<!-- @see me.zhanghai.android.files.file.isSupportedArchive -->
<intent-filter
android:label="@string/archive_viewer_title"
tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/java-archive" />
<data android:mimeType="application/rar" />
<data android:mimeType="application/zip" />
<data android:mimeType="application/zstd" />
<data android:mimeType="application/vnd.android.package-archive" />
<data android:mimeType="application/vnd.debian.binary-package" />
<data android:mimeType="application/vnd.ms-cab-compressed" />
<data android:mimeType="application/vnd.rar" />
<data android:mimeType="application/x-bzip2" />
<data android:mimeType="application/x-compress" />
<data android:mimeType="application/x-cpio" />
<data android:mimeType="application/x-deb" />
<data android:mimeType="application/x-debian-package" />
<data android:mimeType="application/x-gtar" />
<data android:mimeType="application/x-gtar-compressed" />
<data android:mimeType="application/x-java-archive" />
<data android:mimeType="application/x-lzma" />
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/x-7z-compressed" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
<!-- @see https://android.googlesource.com/platform/packages/apps/DocumentsUI/+/main/AndroidManifest.xml -->
<intent-filter>
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.CREATE_DOCUMENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
<!--
~ Unusable until we implement DocumentsProvider.
<intent-filter>
<action android:name="android.intent.action.OPEN_DOCUMENT_TREE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
-->
<intent-filter>
<action android:name="me.zhanghai.android.files.intent.action.VIEW_DOWNLOADS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<!--
~ Using android:documentLaunchMode="always" gives a better result than
~ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK. Not sure why though.
-->
<activity
android:name="me.zhanghai.android.files.filelist.OpenFileActivity"
android:documentLaunchMode="always"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@style/Theme.MaterialFiles.Translucent">
<intent-filter>
<action android:name="me.zhanghai.android.files.intent.action.OPEN_FILE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:name="me.zhanghai.android.files.filelist.EditFileActivity"
android:autoRemoveFromRecents="true"
android:icon="@drawable/edit_icon"
android:label="@string/file_edit_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.filelist.OpenFileAsDialogActivity"
android:autoRemoveFromRecents="true"
android:icon="@drawable/open_as_icon"
android:label="@string/file_open_as_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.AddStorageDialogActivity"
android:label="@string/storage_add_storage_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.EditDeviceStorageDialogActivity"
android:label="@string/storage_edit_device_storage_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.AddExternalStorageShortcutActivity"
android:label="@string/storage_add_external_storage_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.EditExternalStorageShortcutDialogActivity"
android:label="@string/storage_edit_external_storage_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.AddDocumentTreeActivity"
android:label="@string/storage_add_document_tree_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.EditDocumentTreeDialogActivity"
android:label="@string/storage_edit_document_tree_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.storage.EditFtpServerActivity"
android:label="@string/storage_edit_ftp_server_title_edit"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.storage.EditSftpServerActivity"
android:label="@string/storage_edit_sftp_server_title_edit"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.storage.AddLanSmbServerActivity"
android:label="@string/storage_add_lan_smb_server_title"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.storage.EditSmbServerActivity"
android:label="@string/storage_edit_smb_server_title_edit"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.storage.EditWebDavServerActivity"
android:label="@string/storage_edit_webdav_server_title_edit"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.navigation.EditBookmarkDirectoryDialogActivity"
android:label="@string/navigation_edit_bookmark_directory_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.ftpserver.FtpServerActivity"
android:exported="true"
android:label="@string/ftp_server_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles">
<intent-filter>
<action android:name="me.zhanghai.android.files.intent.action.MANAGE_FTP_SERVER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="me.zhanghai.android.files.settings.SettingsActivity"
android:exported="true"
android:label="@string/settings_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="me.zhanghai.android.files.storage.StorageListActivity"
android:label="@string/storage_list_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.settings.StandardDirectoryListActivity"
android:label="@string/settings_standard_directory_list_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.settings.BookmarkDirectoryListActivity"
android:label="@string/settings_bookmark_directory_list_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.about.AboutActivity"
android:label="@string/about_title"
android:launchMode="singleTop"
android:theme="@style/Theme.MaterialFiles" />
<activity
android:name="me.zhanghai.android.files.fileaction.ArchivePasswordDialogActivity"
android:autoRemoveFromRecents="true"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.filejob.FileJobErrorDialogActivity"
android:autoRemoveFromRecents="true"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.filejob.FileJobConflictDialogActivity"
android:autoRemoveFromRecents="true"
android:theme="@style/Theme.MaterialFiles.Translucent" />
<activity
android:name="me.zhanghai.android.files.viewer.saveas.SaveAsActivity"
android:autoRemoveFromRecents="true"
android:exported="true"
android:label="@string/save_as_title"
android:theme="@style/Theme.MaterialFiles.Translucent">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="*/*"
android:scheme="content" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:name="me.zhanghai.android.files.viewer.text.TextEditorActivity"
android:exported="true"
android:label="@string/text_editor_title"
android:theme="@style/Theme.MaterialFiles">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/ecmascript" />
<data android:mimeType="application/javascript" />
<data android:mimeType="application/json" />
<data android:mimeType="application/typescript" />
<data android:mimeType="application/x-sh" />
<data android:mimeType="application/x-shellscript" />
<data android:mimeType="application/xml" />
<data android:mimeType="application/yaml" />
<data android:mimeType="text/*" />
</intent-filter>
</activity>
<activity
android:name="me.zhanghai.android.files.viewer.image.ImageViewerActivity"
android:exported="true"
android:label="@string/image_viewer_title"
android:theme="@style/Theme.MaterialFiles.Immersive">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<service
android:name="me.zhanghai.android.files.filejob.FileJobService"
android:foregroundServiceType="dataSync" />
<service android:name="me.zhanghai.android.files.ftpserver.FtpServerService"
android:foregroundServiceType="dataSync" />
<service
android:name="me.zhanghai.android.files.ftpserver.FtpServerTileService"
android:exported="true"
android:icon="@drawable/shared_directory_icon_white_24dp"
android:label="@string/ftp_server_title"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<provider
android:name="me.zhanghai.android.files.app.AppProvider"
android:authorities="@string/app_provider_authority"
android:exported="false" />
<provider
android:name="me.zhanghai.android.files.file.FileProvider"
android:authorities="@string/file_provider_authority"
android:exported="false"
android:grantUriPermissions="true" />
<receiver android:name="me.zhanghai.android.files.filejob.FileJobReceiver" />
<receiver android:name="me.zhanghai.android.files.ftpserver.FtpServerReceiver" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application>
</manifest>

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.common;
parcelable ParcelableFileTime;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.common;
parcelable ParcelablePosixFileMode;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.common;
parcelable PosixGroup;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.common;
parcelable PosixUser;

View File

@ -1,23 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.IRemoteFileSystem;
import me.zhanghai.android.files.provider.remote.IRemoteFileSystemProvider;
import me.zhanghai.android.files.provider.remote.IRemotePosixFileAttributeView;
import me.zhanghai.android.files.provider.remote.IRemotePosixFileStore;
import me.zhanghai.android.files.provider.remote.ParcelableObject;
interface IRemoteFileService {
IRemoteFileSystemProvider getRemoteFileSystemProviderInterface(String scheme);
IRemoteFileSystem getRemoteFileSystemInterface(in ParcelableObject fileSystem);
IRemotePosixFileStore getRemotePosixFileStoreInterface(in ParcelableObject fileStore);
IRemotePosixFileAttributeView getRemotePosixFileAttributeViewInterface(
in ParcelableObject attributeView
);
void setArchivePasswords(in ParcelableObject fileSystem, in List<String> passwords);
void refreshArchiveFileSystem(in ParcelableObject fileSystem);
}

View File

@ -1,7 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableException;
interface IRemoteFileSystem {
void close(out ParcelableException exception);
}

View File

@ -1,108 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableCopyOptions;
import me.zhanghai.android.files.provider.remote.ParcelableDirectoryStream;
import me.zhanghai.android.files.provider.remote.ParcelableException;
import me.zhanghai.android.files.provider.remote.ParcelableFileAttributes;
import me.zhanghai.android.files.provider.remote.ParcelableObject;
import me.zhanghai.android.files.provider.remote.ParcelablePathListConsumer;
import me.zhanghai.android.files.provider.remote.ParcelableSerializable;
import me.zhanghai.android.files.provider.remote.RemotePathObservable;
import me.zhanghai.android.files.provider.remote.RemoteInputStream;
import me.zhanghai.android.files.provider.remote.RemoteSeekableByteChannel;
import me.zhanghai.android.files.util.RemoteCallback;
interface IRemoteFileSystemProvider {
RemoteInputStream newInputStream(
in ParcelableObject file,
in ParcelableSerializable options,
out ParcelableException exception
);
RemoteSeekableByteChannel newByteChannel(
in ParcelableObject file,
in ParcelableSerializable options,
in ParcelableFileAttributes attributes,
out ParcelableException exception
);
ParcelableDirectoryStream newDirectoryStream(
in ParcelableObject directory,
in ParcelableObject filter,
out ParcelableException exception
);
void createDirectory(
in ParcelableObject directory,
in ParcelableFileAttributes attributes,
out ParcelableException exception
);
void createSymbolicLink(
in ParcelableObject link,
in ParcelableObject target,
in ParcelableFileAttributes attributes,
out ParcelableException exception
);
void createLink(
in ParcelableObject link,
in ParcelableObject existing,
out ParcelableException exception
);
void delete(in ParcelableObject path, out ParcelableException exception);
ParcelableObject readSymbolicLink(in ParcelableObject link, out ParcelableException exception);
RemoteCallback copy(
in ParcelableObject source,
in ParcelableObject target,
in ParcelableCopyOptions options,
in RemoteCallback callback
);
RemoteCallback move(
in ParcelableObject source,
in ParcelableObject target,
in ParcelableCopyOptions options,
in RemoteCallback callback
);
boolean isSameFile(
in ParcelableObject path,
in ParcelableObject path2,
out ParcelableException exception
);
boolean isHidden(in ParcelableObject path, out ParcelableException exception);
ParcelableObject getFileStore(in ParcelableObject path, out ParcelableException exception);
void checkAccess(
in ParcelableObject path,
in ParcelableSerializable modes,
out ParcelableException exception
);
ParcelableObject readAttributes(
in ParcelableObject path,
in ParcelableSerializable type,
in ParcelableSerializable options,
out ParcelableException exception
);
RemotePathObservable observe(
in ParcelableObject path,
long intervalMillis,
out ParcelableException exception
);
RemoteCallback search(
in ParcelableObject directory,
in String query,
long intervalMillis,
in ParcelablePathListConsumer listener,
in RemoteCallback callback
);
}

View File

@ -1,15 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableException;
interface IRemoteInputStream {
int read(out ParcelableException exception);
int read2(out byte[] buffer, out ParcelableException exception);
long skip(long size, out ParcelableException exception);
int available(out ParcelableException exception);
void close(out ParcelableException exception);
}

View File

@ -1,10 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableException;
import me.zhanghai.android.files.util.RemoteCallback;
interface IRemotePathObservable {
void addObserver(in RemoteCallback observer);
void close(out ParcelableException exception);
}

View File

@ -1,29 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.common.ParcelableFileTime;
import me.zhanghai.android.files.provider.common.ParcelablePosixFileMode;
import me.zhanghai.android.files.provider.common.PosixGroup;
import me.zhanghai.android.files.provider.common.PosixUser;
import me.zhanghai.android.files.provider.remote.ParcelableException;
import me.zhanghai.android.files.provider.remote.ParcelableObject;
interface IRemotePosixFileAttributeView {
ParcelableObject readAttributes(out ParcelableException exception);
void setTimes(
in ParcelableFileTime lastModifiedTime,
in ParcelableFileTime lastAccessTime,
in ParcelableFileTime createTime,
out ParcelableException exception
);
void setOwner(in PosixUser owner, out ParcelableException exception);
void setGroup(in PosixGroup group, out ParcelableException exception);
void setMode(in ParcelablePosixFileMode mode, out ParcelableException exception);
void setSeLinuxContext(in ParcelableObject context, out ParcelableException exception);
void restoreSeLinuxContext(out ParcelableException exception);
}

View File

@ -1,13 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableException;
interface IRemotePosixFileStore {
void setReadOnly(boolean readOnly, out ParcelableException exception);
long getTotalSpace(out ParcelableException exception);
long getUsableSpace(out ParcelableException exception);
long getUnallocatedSpace(out ParcelableException exception);
}

View File

@ -1,21 +0,0 @@
package me.zhanghai.android.files.provider.remote;
import me.zhanghai.android.files.provider.remote.ParcelableException;
interface IRemoteSeekableByteChannel {
int read(out byte[] destination, out ParcelableException exception);
int write(in byte[] source, out ParcelableException exception);
long position(out ParcelableException exception);
void position2(long newPosition, out ParcelableException exception);
long size(out ParcelableException exception);
void truncate(long size, out ParcelableException exception);
void force(boolean metaData, out ParcelableException exception);
void close(out ParcelableException exception);
}

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableCopyOptions;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableDirectoryStream;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableException;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableFileAttributes;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableObject;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelablePathListConsumer;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable ParcelableSerializable;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable RemoteInputStream;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable RemotePathObservable;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.provider.remote;
parcelable RemoteSeekableByteChannel;

View File

@ -1,7 +0,0 @@
package me.zhanghai.android.files.util;
import android.os.Bundle;
interface IRemoteCallback {
void sendResult(in Bundle result);
}

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.util;
parcelable ParcelSlicedList;

View File

@ -1,3 +0,0 @@
package me.zhanghai.android.files.util;
parcelable RemoteCallback;

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package androidx.appcompat.app;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
public class AppCompatDelegateCompat {
private AppCompatDelegateCompat() {}
@SuppressLint("RestrictedApi")
public static int mapNightMode(@NonNull AppCompatDelegate delegate, @NonNull Context context,
int mode) {
return ((AppCompatDelegateImpl) delegate).mapNightMode(context, mode);
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package androidx.appcompat.widget;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
public class FixPaddingListPopupWindow extends ListPopupWindow {
public FixPaddingListPopupWindow(@NonNull Context context) {
super(context);
}
public FixPaddingListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public FixPaddingListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FixPaddingListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@NonNull
@Override
DropDownListView createDropDownListView(@NonNull Context context, boolean hijackFocus) {
return new FixPaddingDropDownListView(context, hijackFocus);
}
private static class FixPaddingDropDownListView extends DropDownListView {
public FixPaddingDropDownListView(Context context, boolean hijackFocus) {
super(context, hijackFocus);
}
// DropDownListView.measureHeightOfChildrenCompat() uses list padding instead of regular
// padding, which isn't initialized before onMeasure() so returns no padding for the first
// time. And ListPopupWindow.buildDropDown() adds the regular padding back every time, which
// will double the padding after the first show.
@Override
public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
int endPosition, int maxHeight,
int disallowPartialChildPosition) {
int height = super.measureHeightOfChildrenCompat(widthMeasureSpec, startPosition,
endPosition, maxHeight, disallowPartialChildPosition);
height -= getListPaddingTop() + getListPaddingBottom();
return height;
}
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package androidx.swiperefreshlayout.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.ShapeDrawable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import me.zhanghai.android.files.R;
import me.zhanghai.android.files.compat.ContextCompatKt;
import me.zhanghai.android.files.util.ContextExtensionsKt;
public class ThemedSwipeRefreshLayout extends SwipeRefreshLayout {
public ThemedSwipeRefreshLayout(@NonNull Context context) {
super(context);
init();
}
public ThemedSwipeRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
Context context = getContext();
boolean isMaterial3Theme = ContextExtensionsKt.isMaterial3Theme(context);
int backgroundColor;
if (isMaterial3Theme) {
int surfaceColor = ContextExtensionsKt.getColorByAttr(context,
com.google.android.material.R.attr.colorSurface);
@SuppressLint("PrivateResource")
int overlayColor = ContextCompatKt.getColorCompat(context,
com.google.android.material.R.color.m3_popupmenu_overlay_color);
backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor);
} else {
backgroundColor = ContextExtensionsKt.getColorByAttr(context,
androidx.appcompat.R.attr.colorBackgroundFloating);
}
((ShapeDrawable) mCircleView.getBackground()).getPaint().setColor(backgroundColor);
setColorSchemeColors(ContextExtensionsKt.getColorByAttr(context,
androidx.appcompat.R.attr.colorAccent));
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
View child = getChildView();
if (child != null) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(child.getMeasuredWidth() + getPaddingLeft() + getPaddingRight(),
child.getMeasuredHeight() + getPaddingTop() + getPaddingBottom());
}
}
@Nullable
private View getChildView() {
for (int i = 0; i < getChildCount(); ++i) {
View child = getChildAt(i);
if (!child.equals(mCircleView)) {
return child;
}
}
return null;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2024 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package at.bitfire.dav4jvm;
import java.io.IOException;
import androidx.annotation.NonNull;
import at.bitfire.dav4jvm.exception.DavException;
import at.bitfire.dav4jvm.exception.HttpException;
import kotlin.jvm.functions.Function0;
import okhttp3.Response;
public class DavResourceAccessor {
private DavResourceAccessor() {}
public static void checkStatus(@NonNull DavResource davResource, @NonNull Response response)
throws HttpException {
davResource.checkStatus(response);
}
public static Response followRedirects(@NonNull DavResource davResource,
@NonNull Function0<Response> sendRequest) throws DavException, IOException {
return davResource.followRedirects$build(sendRequest);
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2022 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package com.google.android.material.appbar;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.WindowInsetsCompat;
public class OnWindowInsetChangedAppBarLayout extends AppBarLayout {
public OnWindowInsetChangedAppBarLayout(@NonNull Context context) {
super(context);
}
public OnWindowInsetChangedAppBarLayout(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public OnWindowInsetChangedAppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public WindowInsetsCompat onWindowInsetChanged(@NonNull WindowInsetsCompat insets) {
return super.onWindowInsetChanged(insets);
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2021 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package com.google.android.material.shape;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.elevation.ElevationOverlayProvider;
public class MaterialShapeDrawableAccessor {
private MaterialShapeDrawableAccessor() {}
@SuppressLint("RestrictedApi")
public static ElevationOverlayProvider getElevationOverlayProvider(
@NonNull MaterialShapeDrawable drawable) {
MaterialShapeDrawable.MaterialShapeDrawableState drawableState =
(MaterialShapeDrawable.MaterialShapeDrawableState) drawable.getConstantState();
return drawableState.elevationOverlayProvider;
}
@SuppressLint("RestrictedApi")
public static void setElevationOverlayProvider(
@NonNull MaterialShapeDrawable drawable,
@Nullable ElevationOverlayProvider elevationOverlayProvider) {
MaterialShapeDrawable.MaterialShapeDrawableState drawableState =
(MaterialShapeDrawable.MaterialShapeDrawableState) drawable.getConstantState();
drawableState.elevationOverlayProvider = elevationOverlayProvider;
}
public static void updateZ(@NonNull MaterialShapeDrawable drawable) {
final float parentAbsoluteElevation = drawable.getParentAbsoluteElevation();
drawable.setParentAbsoluteElevation(parentAbsoluteElevation + 1);
drawable.setParentAbsoluteElevation(parentAbsoluteElevation);
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package com.hierynomus.smbj.share;
import com.hierynomus.mssmb2.messages.SMB2ReadResponse;
import com.hierynomus.smbj.common.SMBRuntimeException;
import java.util.concurrent.Future;
import androidx.annotation.NonNull;
public class FileAccessor {
private FileAccessor() {}
/**
* @see File#readAsync(long, int)
*/
@NonNull
public static Future<SMB2ReadResponse> readAsync(@NonNull File file, long offset, int length)
throws SMBRuntimeException {
return file.readAsync(offset, length);
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package com.hierynomus.smbj.share;
import androidx.annotation.NonNull;
import com.hierynomus.mssmb2.SMB2FileId;
import com.hierynomus.mssmb2.messages.SMB2IoctlResponse;
import com.hierynomus.smbj.io.ArrayByteChunkProvider;
import com.hierynomus.smbj.io.ByteChunkProvider;
import java.util.concurrent.Future;
public class ShareAccessor {
private ShareAccessor() {}
/**
* This ioctl() variant allows passing in the {@param statusHandler}.
*
* @see Share#ioctl(com.hierynomus.mssmb2.SMB2FileId, long, boolean, ByteChunkProvider, int)
*/
@NonNull
public static SMB2IoctlResponse ioctl(@NonNull Share share, @NonNull SMB2FileId fileId,
long ctlCode, boolean isFsCtl, @NonNull byte[] inData,
int inOffset, int inLength,
@NonNull StatusHandler statusHandler, long timeout) {
final ByteChunkProvider inputData = new ArrayByteChunkProvider(inData, inOffset, inLength,
0);
final Future<SMB2IoctlResponse> future = share.ioctlAsync(fileId, ctlCode, isFsCtl,
inputData, -1);
return share.receive(future, "IOCTL", fileId, statusHandler, timeout);
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.about
import android.os.Bundle
import android.view.View
import androidx.fragment.app.add
import androidx.fragment.app.commit
import me.zhanghai.android.files.app.AppActivity
class AboutActivity : AppActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Calls ensureSubDecor().
findViewById<View>(android.R.id.content)
if (savedInstanceState == null) {
supportFragmentManager.commit { add<AboutFragment>(android.R.id.content) }
}
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.about
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import me.zhanghai.android.files.databinding.AboutFragmentBinding
import me.zhanghai.android.files.ui.LicensesDialogFragment
import me.zhanghai.android.files.util.createViewIntent
import me.zhanghai.android.files.util.startActivitySafe
class AboutFragment : Fragment() {
private lateinit var binding: AboutFragmentBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
AboutFragmentBinding.inflate(inflater, container, false)
.also { binding = it }
.root
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val activity = requireActivity() as AppCompatActivity
activity.setSupportActionBar(binding.toolbar)
activity.supportActionBar!!.setDisplayHomeAsUpEnabled(true)
binding.gitHubLayout.setOnClickListener { startActivitySafe(GITHUB_URI.createViewIntent()) }
binding.licensesLayout.setOnClickListener { LicensesDialogFragment.show(this) }
//#ifdef NONFREE
binding.privacyPolicyLayout.isVisible = true
binding.privacyPolicyLayout.setOnClickListener {
startActivitySafe(PRIVACY_POLICY_URI.createViewIntent())
}
//#endif
binding.authorNameLayout.setOnClickListener {
startActivitySafe(AUTHOR_RESUME_URI.createViewIntent())
}
binding.authorGitHubLayout.setOnClickListener {
startActivitySafe(AUTHOR_GITHUB_URI.createViewIntent())
}
binding.authorTwitterLayout.setOnClickListener {
startActivitySafe(AUTHOR_TWITTER_URI.createViewIntent())
}
}
companion object {
private val GITHUB_URI = Uri.parse("https://github.com/zhanghai/MaterialFiles")
private val PRIVACY_POLICY_URI =
Uri.parse("https://github.com/zhanghai/MaterialFiles/blob/master/PRIVACY.md")
private val AUTHOR_RESUME_URI = Uri.parse("https://resume.zhanghai.me/")
private val AUTHOR_GITHUB_URI = Uri.parse("https://github.com/zhanghai")
private val AUTHOR_TWITTER_URI = Uri.parse("https://twitter.com/zhanghai95")
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import me.zhanghai.android.files.theme.custom.CustomThemeHelper
import me.zhanghai.android.files.theme.night.NightModeHelper
abstract class AppActivity : AppCompatActivity() {
private var isDelegateCreated = false
override fun getDelegate(): AppCompatDelegate {
val delegate = super.getDelegate()
if (!isDelegateCreated) {
isDelegateCreated = true
NightModeHelper.apply(this)
}
return delegate
}
override fun onCreate(savedInstanceState: Bundle?) {
CustomThemeHelper.apply(this)
super.onCreate(savedInstanceState)
}
override fun onSupportNavigateUp(): Boolean {
if (!super.onSupportNavigateUp()) {
finish()
}
return true
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.os.AsyncTask
import android.os.Build
import android.webkit.WebView
import com.jakewharton.threetenabp.AndroidThreeTen
import jcifs.context.SingletonContext
import me.zhanghai.android.files.BuildConfig
import me.zhanghai.android.files.coil.initializeCoil
import me.zhanghai.android.files.filejob.fileJobNotificationTemplate
import me.zhanghai.android.files.ftpserver.ftpServerServiceNotificationTemplate
import me.zhanghai.android.files.hiddenapi.HiddenApi
import me.zhanghai.android.files.provider.FileSystemProviders
import me.zhanghai.android.files.settings.Settings
import me.zhanghai.android.files.storage.FtpServerAuthenticator
import me.zhanghai.android.files.storage.SftpServerAuthenticator
import me.zhanghai.android.files.storage.SmbServerAuthenticator
import me.zhanghai.android.files.storage.StorageVolumeListLiveData
import me.zhanghai.android.files.storage.WebDavServerAuthenticator
import me.zhanghai.android.files.theme.custom.CustomThemeHelper
import me.zhanghai.android.files.theme.night.NightModeHelper
import java.util.Properties
import me.zhanghai.android.files.provider.ftp.client.Client as FtpClient
import me.zhanghai.android.files.provider.sftp.client.Client as SftpClient
import me.zhanghai.android.files.provider.smb.client.Client as SmbClient
import me.zhanghai.android.files.provider.webdav.client.Client as WebDavClient
val appInitializers = listOf(
::initializeCrashlytics, ::disableHiddenApiChecks, ::initializeThreeTen,
::initializeWebViewDebugging, ::initializeCoil, ::initializeFileSystemProviders, ::upgradeApp,
::initializeLiveDataObjects, ::initializeCustomTheme, ::initializeNightMode,
::createNotificationChannels
)
private fun initializeCrashlytics() {
//#ifdef NONFREE
me.zhanghai.android.files.nonfree.CrashlyticsInitializer.initialize()
//#endif
}
private fun disableHiddenApiChecks() {
HiddenApi.disableHiddenApiChecks()
}
private fun initializeThreeTen() {
AndroidThreeTen.init(application)
}
private fun initializeWebViewDebugging() {
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
}
}
private fun initializeFileSystemProviders() {
FileSystemProviders.install()
FileSystemProviders.overflowWatchEvents = true
// SingletonContext.init() calls NameServiceClientImpl.initCache() which connects to network.
AsyncTask.THREAD_POOL_EXECUTOR.execute {
SingletonContext.init(
Properties().apply {
setProperty("jcifs.netbios.cachePolicy", "0")
setProperty("jcifs.smb.client.maxVersion", "SMB1")
}
)
}
FtpClient.authenticator = FtpServerAuthenticator
SftpClient.authenticator = SftpServerAuthenticator
SmbClient.authenticator = SmbServerAuthenticator
WebDavClient.authenticator = WebDavServerAuthenticator
}
private fun initializeLiveDataObjects() {
// Force initialization of LiveData objects so that it won't happen on a background thread.
StorageVolumeListLiveData.value
Settings.FILE_LIST_DEFAULT_DIRECTORY.value
}
private fun initializeCustomTheme() {
CustomThemeHelper.initialize(application)
}
private fun initializeNightMode() {
NightModeHelper.initialize(application)
}
private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannels(
listOf(
backgroundActivityStartNotificationTemplate.channelTemplate,
fileJobNotificationTemplate.channelTemplate,
ftpServerServiceNotificationTemplate.channelTemplate
).map { it.create(application) }
)
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.app.Application
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
lateinit var application: Application private set
class AppProvider : ContentProvider() {
override fun onCreate(): Boolean {
application = context as Application
appInitializers.forEach { it() }
return true
}
override fun query(
uri: Uri,
projection: Array<String?>?,
selection: String?,
selectionArgs: Array<String?>?,
sortOrder: String?
): Cursor? {
throw UnsupportedOperationException()
}
override fun getType(uri: Uri): String? {
throw UnsupportedOperationException()
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException()
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String?>?): Int {
throw UnsupportedOperationException()
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String?>?
): Int {
throw UnsupportedOperationException()
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import androidx.core.content.edit
import me.zhanghai.android.files.BuildConfig
private const val KEY_VERSION_CODE = "key_version_code"
private const val VERSION_CODE_BELOW_1_1_0 = 17
private const val VERSION_CODE_1_1_0 = 18
private const val VERSION_CODE_1_2_0 = 22
private const val VERSION_CODE_1_3_0 = 24
private const val VERSION_CODE_1_4_0 = 26
private const val VERSION_CODE_1_5_0 = 29
private const val VERSION_CODE_1_6_0 = 32
private const val VERSION_CODE_1_7_2 = 37
private const val VERSION_CODE_LATEST = BuildConfig.VERSION_CODE
private var lastVersionCode: Int
get() {
if (defaultSharedPreferences.all.isEmpty()) {
// This is a new install.
lastVersionCode = VERSION_CODE_LATEST
return VERSION_CODE_LATEST
}
return defaultSharedPreferences.getInt(KEY_VERSION_CODE, VERSION_CODE_BELOW_1_1_0)
}
set(value) {
defaultSharedPreferences.edit { putInt(KEY_VERSION_CODE, value) }
}
fun upgradeApp() {
upgradeAppFrom(lastVersionCode)
lastVersionCode = VERSION_CODE_LATEST
}
private fun upgradeAppFrom(lastVersionCode: Int) {
if (lastVersionCode < VERSION_CODE_1_1_0) {
upgradeAppTo1_1_0()
}
if (lastVersionCode < VERSION_CODE_1_2_0) {
upgradeAppTo1_2_0()
}
if (lastVersionCode < VERSION_CODE_1_3_0) {
upgradeAppTo1_3_0()
}
if (lastVersionCode < VERSION_CODE_1_4_0) {
upgradeAppTo1_4_0()
}
if (lastVersionCode < VERSION_CODE_1_5_0) {
upgradeAppTo1_5_0()
}
if (lastVersionCode < VERSION_CODE_1_6_0) {
upgradeAppTo1_6_0()
}
if (lastVersionCode < VERSION_CODE_1_7_2) {
upgradeAppTo1_7_2()
}
// Continue with new `if`s on lastVersionCode instead of `else if`.
}

View File

@ -1,632 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.core.content.edit
import me.zhanghai.android.files.R
import me.zhanghai.android.files.compat.PreferenceManagerCompat
import me.zhanghai.android.files.compat.getDescriptionCompat
import me.zhanghai.android.files.compat.readBooleanCompat
import me.zhanghai.android.files.compat.writeBooleanCompat
import me.zhanghai.android.files.compat.writeParcelableListCompat
import me.zhanghai.android.files.file.DocumentTreeUri
import me.zhanghai.android.files.file.asExternalStorageUriOrNull
import me.zhanghai.android.files.file.displayName
import me.zhanghai.android.files.file.storageVolume
import me.zhanghai.android.files.filelist.FileSortOptions
import me.zhanghai.android.files.navigation.BookmarkDirectory
import me.zhanghai.android.files.navigation.StandardDirectorySettings
import me.zhanghai.android.files.provider.archive.ArchiveFileSystem
import me.zhanghai.android.files.provider.common.ByteString
import me.zhanghai.android.files.provider.common.moveToByteString
import me.zhanghai.android.files.provider.content.ContentFileSystem
import me.zhanghai.android.files.provider.document.DocumentFileSystem
import me.zhanghai.android.files.provider.document.resolver.ExternalStorageProviderHacks
import me.zhanghai.android.files.provider.linux.LinuxFileSystem
import me.zhanghai.android.files.provider.root.RootStrategy
import me.zhanghai.android.files.provider.sftp.SftpFileSystem
import me.zhanghai.android.files.provider.smb.SmbFileSystem
import me.zhanghai.android.files.storage.DocumentTree
import me.zhanghai.android.files.storage.FileSystemRoot
import me.zhanghai.android.files.storage.PrimaryStorageVolume
import me.zhanghai.android.files.util.StableUriParceler
import me.zhanghai.android.files.util.asBase64
import me.zhanghai.android.files.util.readParcelable
import me.zhanghai.android.files.util.readParcelableListCompat
import me.zhanghai.android.files.util.toBase64
import me.zhanghai.android.files.util.toByteArray
import me.zhanghai.android.files.util.use
internal fun upgradeAppTo1_1_0() {
// Migrate settings.
migratePathSetting1_1_0(R.string.pref_key_file_list_default_directory)
migrateFileSortOptionsSetting1_1_0()
migrateCreateArchiveTypeSetting1_1_0()
migrateStandardDirectorySettingsSetting1_1_0()
migrateBookmarkDirectoriesSetting1_1_0()
migratePathSetting1_1_0(R.string.pref_key_ftp_server_home_directory)
for (key in pathSharedPreferences.all.keys) {
migrateFileSortOptionsSetting1_1_0(pathSharedPreferences, key)
}
}
private const val PARCEL_VAL_PARCELABLE = 4
private const val PARCEL_VAL_LIST = 11
private fun migratePathSetting1_1_0(@StringRes keyRes: Int) {
val key = application.getString(keyRes)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
migratePath1_1_0(oldParcel, newParcel)
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun migrateFileSortOptionsSetting1_1_0() {
migrateFileSortOptionsSetting1_1_0(
defaultSharedPreferences, application.getString(R.string.pref_key_file_list_sort_options)
)
}
private fun migrateFileSortOptionsSetting1_1_0(sharedPreferences: SharedPreferences, key: String) {
val oldBytes = sharedPreferences.getString(key, null)?.asBase64()?.toByteArray() ?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeString(oldParcel.readString())
newParcel.writeString(FileSortOptions.By.entries[oldParcel.readInt()].name)
newParcel.writeString(FileSortOptions.Order.entries[oldParcel.readInt()].name)
newParcel.writeInt(oldParcel.readByte().toInt())
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
sharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
fun migrateCreateArchiveTypeSetting1_1_0() {
val key = application.getString(R.string.pref_key_create_archive_type)
val oldValue = defaultSharedPreferences.getString(key, null) ?: return
val newValue = oldValue.replace(Regex("type_.+$")) {
when (it.value) {
"type_zip" -> "zipRadio"
"type_tar_xz" -> "tarXzRadio"
"type_seven_z" -> "sevenZRadio"
else -> "zipRadio"
}
}
defaultSharedPreferences.edit { putString(key, newValue) }
}
private fun migrateStandardDirectorySettingsSetting1_1_0() {
val key = application.getString(R.string.pref_key_standard_directory_settings)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
newParcel.writeInt(PARCEL_VAL_LIST)
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
oldParcel.readInt()
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
newParcel.writeString(StandardDirectorySettings::class.java.name)
newParcel.writeString(oldParcel.readString())
newParcel.writeString(oldParcel.readString())
newParcel.writeInt(oldParcel.readByte().toInt())
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun migrateBookmarkDirectoriesSetting1_1_0() {
val key = application.getString(R.string.pref_key_bookmark_directories)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
newParcel.writeInt(PARCEL_VAL_LIST)
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
oldParcel.readInt()
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
newParcel.writeString(BookmarkDirectory::class.java.name)
newParcel.writeLong(oldParcel.readLong())
newParcel.writeString(oldParcel.readString())
migratePath1_1_0(oldParcel, newParcel)
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private val oldByteStringCreator = object : Parcelable.Creator<ByteString> {
override fun createFromParcel(source: Parcel): ByteString =
source.createByteArray()!!.moveToByteString()
override fun newArray(size: Int): Array<ByteString?> = arrayOfNulls(size)
}
private fun migratePath1_1_0(oldParcel: Parcel, newParcel: Parcel) {
val className = oldParcel.readString()
newParcel.writeString(className)
newParcel.writeByte(oldParcel.readByte())
newParcel.writeBooleanCompat(oldParcel.readByte() != 0.toByte())
newParcel.writeParcelableListCompat(oldParcel.createTypedArrayList(oldByteStringCreator), 0)
when (className) {
"me.zhanghai.android.files.provider.archive.ArchivePath" -> {
oldParcel.readString()
newParcel.writeString(ArchiveFileSystem::class.java.name)
migratePath1_1_0(oldParcel, newParcel)
}
"me.zhanghai.android.files.provider.content.ContentPath" -> {
oldParcel.readString()
newParcel.writeString(ContentFileSystem::class.java.name)
newParcel.writeParcelable(oldParcel.readParcelable<Uri>(), 0)
}
"me.zhanghai.android.files.provider.document.DocumentPath" -> {
oldParcel.readString()
newParcel.writeString(DocumentFileSystem::class.java.name)
newParcel.writeParcelable(oldParcel.readParcelable<Uri>(), 0)
}
"me.zhanghai.android.files.provider.linux.LinuxPath" -> {
oldParcel.readString()
newParcel.writeString(LinuxFileSystem::class.java.name)
newParcel.writeBooleanCompat(oldParcel.readByte() != 0.toByte())
}
else -> throw IllegalStateException(className)
}
}
private val pathSharedPreferences: SharedPreferences
get() {
val name = "${PreferenceManagerCompat.getDefaultSharedPreferencesName(application)}_path"
val mode = PreferenceManagerCompat.defaultSharedPreferencesMode
return application.getSharedPreferences(name, mode)
}
internal fun upgradeAppTo1_2_0() {
migrateStoragesSetting1_2_0()
}
private fun migrateStoragesSetting1_2_0() {
val key = application.getString(R.string.pref_key_storages)
val storages = (listOf(FileSystemRoot(null, true), PrimaryStorageVolume(null, true))
+ DocumentTreeUri.persistedUris.map {
DocumentTree(
null, it.storageVolume?.getDescriptionCompat(application) ?: it.displayName
?: it.value.toString(), it
)
})
val bytes = Parcel.obtain().use { parcel ->
parcel.writeValue(storages)
parcel.marshall()
}
defaultSharedPreferences.edit { putString(key, bytes.toBase64().value) }
}
internal fun upgradeAppTo1_3_0() {
migrateSmbServersSetting1_3_0()
}
private fun migrateSmbServersSetting1_3_0() {
val key = application.getString(R.string.pref_key_storages)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
newParcel.writeInt(PARCEL_VAL_LIST)
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
val oldPosition = oldParcel.dataPosition()
oldParcel.readInt()
val className = oldParcel.readString()
if (className == "me.zhanghai.android.files.storage.SmbServer") {
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
newParcel.writeString("me.zhanghai.android.files.storage.SmbServer")
val id = oldParcel.readLong()
newParcel.writeLong(id)
val customName = oldParcel.readString()
newParcel.writeString(customName)
oldParcel.readString()
newParcel.writeString(
"me.zhanghai.android.files.provider.smb.client.Authority"
)
val authorityHost = oldParcel.readString()
newParcel.writeString(authorityHost)
val authorityPort = oldParcel.readInt()
newParcel.writeInt(authorityPort)
oldParcel.readString()
newParcel.writeString(
"me.zhanghai.android.files.provider.smb.client.Authentication"
)
val authenticationUsername = oldParcel.readString()
newParcel.writeString(authenticationUsername)
val authenticationDomain = oldParcel.readString()
newParcel.writeString(authenticationDomain)
val authenticationPassword = oldParcel.readString()
newParcel.writeString(authenticationPassword)
val relativePath = ""
newParcel.writeString(relativePath)
} else {
oldParcel.setDataPosition(oldPosition)
val storage = oldParcel.readValue(appClassLoader)
newParcel.writeValue(storage)
}
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
internal fun upgradeAppTo1_4_0() {
migratePathSetting1_4_0(R.string.pref_key_file_list_default_directory)
migrateSftpServersSetting1_4_0()
migrateBookmarkDirectoriesSetting1_4_0()
migrateRootStrategySetting1_4_0()
migratePathSetting1_4_0(R.string.pref_key_ftp_server_home_directory)
}
private fun migratePathSetting1_4_0(@StringRes keyRes: Int) {
val key = application.getString(keyRes)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
migratePath1_4_0(oldParcel, newParcel)
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun migrateBookmarkDirectoriesSetting1_4_0() {
val key = application.getString(R.string.pref_key_bookmark_directories)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
newParcel.writeInt(oldParcel.readInt())
newParcel.writeString(oldParcel.readString())
newParcel.writeLong(oldParcel.readLong())
newParcel.writeString(oldParcel.readString())
migratePath1_4_0(oldParcel, newParcel)
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun migratePath1_4_0(oldParcel: Parcel, newParcel: Parcel) {
val className = oldParcel.readString()
newParcel.writeString(className)
newParcel.writeByte(oldParcel.readByte())
newParcel.writeBooleanCompat(oldParcel.readBooleanCompat())
newParcel.writeParcelableListCompat(oldParcel.readParcelableListCompat<ByteString>(), 0)
when (className) {
"me.zhanghai.android.files.provider.archive.ArchivePath" -> {
newParcel.writeString(oldParcel.readString())
migratePath1_4_0(oldParcel, newParcel)
}
"me.zhanghai.android.files.provider.content.ContentPath" -> {
newParcel.writeParcelable(oldParcel.readParcelable<ContentFileSystem>(), 0)
newParcel.writeParcelable(oldParcel.readParcelable<Uri>(), 0)
}
"me.zhanghai.android.files.provider.document.DocumentPath" ->
newParcel.writeParcelable(oldParcel.readParcelable<DocumentFileSystem>(), 0)
"me.zhanghai.android.files.provider.linux.LinuxPath" -> {
newParcel.writeParcelable(oldParcel.readParcelable<LinuxFileSystem>(), 0)
oldParcel.readBooleanCompat()
}
"me.zhanghai.android.files.provider.sftp.SftpPath" ->
newParcel.writeParcelable(oldParcel.readParcelable<SftpFileSystem>(), 0)
"me.zhanghai.android.files.provider.smb.SmbPath" ->
newParcel.writeParcelable(oldParcel.readParcelable<SmbFileSystem>(), 0)
else -> throw IllegalStateException(className)
}
}
private fun migrateSftpServersSetting1_4_0() {
val key = application.getString(R.string.pref_key_storages)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
val oldPosition = oldParcel.dataPosition()
oldParcel.readInt()
when (oldParcel.readString()) {
"me.zhanghai.android.files.storage.SftpServer" -> {
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
newParcel.writeString("me.zhanghai.android.files.storage.SftpServer")
val id = oldParcel.readLong()
newParcel.writeLong(id)
val customName = oldParcel.readString()
newParcel.writeString(customName)
val authorityHost = oldParcel.readString()
newParcel.writeString(authorityHost)
val authorityPort = oldParcel.readInt()
newParcel.writeInt(authorityPort)
val authenticationClassName = oldParcel.readString()
val authorityUsername = oldParcel.readString()
newParcel.writeString(authorityUsername)
newParcel.writeString(authenticationClassName)
val authenticationPasswordOrPrivateKey = oldParcel.readString()
newParcel.writeString(authenticationPasswordOrPrivateKey)
val relativePath = oldParcel.readString()
newParcel.writeString(relativePath)
}
else -> {
oldParcel.setDataPosition(oldPosition)
val storage = oldParcel.readValue(appClassLoader)
newParcel.writeValue(storage)
}
}
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun migrateRootStrategySetting1_4_0() {
val key = application.getString(R.string.pref_key_root_strategy)
val oldValue = defaultSharedPreferences.getString(key, null)?.toInt() ?: return
val newValue = when (oldValue) {
0 -> RootStrategy.NEVER
3 -> RootStrategy.ALWAYS
else -> RootStrategy.AUTOMATIC
}.ordinal.toString()
defaultSharedPreferences.edit { putString(key, newValue) }
}
internal fun upgradeAppTo1_5_0() {
migrateSftpServersSetting1_5_0()
}
private fun migrateSftpServersSetting1_5_0() {
val key = application.getString(R.string.pref_key_storages)
val oldBytes = defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray()
?: return
val newBytes = try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
val oldPosition = oldParcel.dataPosition()
oldParcel.readInt()
when (oldParcel.readString()) {
"me.zhanghai.android.files.storage.SftpServer" -> {
newParcel.writeInt(PARCEL_VAL_PARCELABLE)
newParcel.writeString("me.zhanghai.android.files.storage.SftpServer")
val id = oldParcel.readLong()
newParcel.writeLong(id)
val customName = oldParcel.readString()
newParcel.writeString(customName)
val authorityHost = oldParcel.readString()
newParcel.writeString(authorityHost)
val authorityPort = oldParcel.readInt()
newParcel.writeInt(authorityPort)
val authorityUsername = oldParcel.readString()
newParcel.writeString(authorityUsername)
val authenticationClassName = oldParcel.readString()
newParcel.writeString(authenticationClassName)
val authenticationPasswordOrPrivateKey = oldParcel.readString()
newParcel.writeString(authenticationPasswordOrPrivateKey)
if (authenticationClassName == "me.zhanghai.android.files.provider.sftp"
+ ".client.PublicKeyAuthentication") {
newParcel.writeString(null)
}
val relativePath = oldParcel.readString()
newParcel.writeString(relativePath)
}
else -> {
oldParcel.setDataPosition(oldPosition)
val storage = oldParcel.readValue(appClassLoader)
newParcel.writeValue(storage)
}
}
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
internal fun upgradeAppTo1_6_0() {
addViewTypePathSetting1_6_0()
}
private fun addViewTypePathSetting1_6_0() {
val keys = pathSharedPreferences.all.keys.toSet()
val sortOptionsKey = application.getString(R.string.pref_key_file_list_sort_options)
val viewTypeKey = application.getString(R.string.pref_key_file_list_view_type)
val defaultViewType = application.getString(R.string.pref_default_value_file_list_view_type)
for (key in keys) {
if (!key.startsWith(sortOptionsKey)) {
continue
}
val newKey = key.replaceFirst(sortOptionsKey, viewTypeKey)
if (newKey in keys) {
continue
}
pathSharedPreferences.edit { putString(newKey, defaultViewType) }
}
}
internal fun upgradeAppTo1_7_2() {
migrateDocumentManagerShortcutSetting1_7_2()
}
private fun migrateDocumentManagerShortcutSetting1_7_2() {
val key = application.getString(R.string.pref_key_storages)
val oldBytes =
defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray() ?: return
val newBytes =
try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
readWriteLengthPrefixedValue(oldParcel, newParcel) {
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
val oldPosition = oldParcel.dataPosition()
oldParcel.readInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Skip prefix length.
oldParcel.readInt()
}
val className = oldParcel.readString()
oldParcel.setDataPosition(oldPosition)
when (className) {
"me.zhanghai.android.files.storage.DocumentManagerShortcut" -> {
newParcel.writeInt(oldParcel.readInt())
readWriteLengthPrefixedValue(oldParcel, newParcel) {
oldParcel.readString()
newParcel.writeString(
"me.zhanghai.android.files.storage" +
".ExternalStorageShortcut"
)
val id = oldParcel.readLong()
newParcel.writeLong(id)
val customName = oldParcel.readString()
newParcel.writeString(customName)
var uri = StableUriParceler.create(oldParcel)!!
if (uri.asExternalStorageUriOrNull() == null) {
// Reset to a valid external storage URI.
uri =
ExternalStorageProviderHacks
.DOCUMENT_URI_ANDROID_DATA
}
with(StableUriParceler) { uri.write(newParcel, 0) }
}
}
else -> {
val storage = oldParcel.readValue(appClassLoader)
newParcel.writeValue(storage)
}
}
}
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}
private fun readWriteLengthPrefixedValue(oldParcel: Parcel, newParcel: Parcel, block: () -> Unit) {
var lengthPosition = 0
var startPosition = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
oldParcel.readInt()
lengthPosition = newParcel.dataPosition()
newParcel.writeInt(-1)
startPosition = newParcel.dataPosition()
}
block()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val endPosition = newParcel.dataPosition()
newParcel.setDataPosition(lengthPosition)
newParcel.writeInt(endPosition - startPosition)
newParcel.setDataPosition(endPosition)
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import me.zhanghai.android.files.R
import me.zhanghai.android.files.util.NotificationChannelTemplate
import me.zhanghai.android.files.util.NotificationTemplate
import me.zhanghai.android.files.util.startActivitySafe
val backgroundActivityStartNotificationTemplate =
NotificationTemplate(
NotificationChannelTemplate(
"background_activity_start",
R.string.notification_channel_background_activity_start_name,
NotificationManagerCompat.IMPORTANCE_HIGH,
descriptionRes = R.string.notification_channel_background_activity_start_description,
showBadge = false
),
colorRes = R.color.color_primary,
smallIcon = R.drawable.notification_icon,
ongoing = true,
autoCancel = true,
category = NotificationCompat.CATEGORY_ERROR,
priority = NotificationCompat.PRIORITY_HIGH
)
object BackgroundActivityStarter {
fun startActivity(intent: Intent, title: CharSequence, text: CharSequence?, context: Context) {
// TODO: Only use new task when in background?
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (isInForeground) {
context.startActivitySafe(intent)
} else {
notifyStartActivity(intent, title, text, context)
}
}
private val isInForeground: Boolean
get() = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(
Lifecycle.State.STARTED
)
private fun notifyStartActivity(
intent: Intent,
title: CharSequence,
text: CharSequence?,
context: Context
) {
var pendingIntentFlags = PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_CANCEL_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent = PendingIntent.getActivity(
context, intent.hashCode(), intent, pendingIntentFlags
)
val notification = backgroundActivityStartNotificationTemplate.createBuilder(context)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)
.build()
notificationManager.notify(intent.hashCode(), notification)
}
}

View File

@ -1,10 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
object NotificationIds {
const val FTP_SERVER = 1
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.app
import android.content.ClipboardManager
import android.content.ContentResolver
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.wifi.WifiManager
import android.os.PowerManager
import android.os.storage.StorageManager
import android.view.inputmethod.InputMethodManager
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import me.zhanghai.android.files.compat.getSystemServiceCompat
import me.zhanghai.android.files.compat.mainExecutorCompat
import okhttp3.OkHttpClient
import java.util.concurrent.Executor
val appClassLoader = AppProvider::class.java.classLoader
val clipboardManager: ClipboardManager by lazy {
application.getSystemServiceCompat(ClipboardManager::class.java)
}
val contentResolver: ContentResolver by lazy { application.contentResolver }
val defaultSharedPreferences: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(application)
}
val okHttpClient: OkHttpClient by lazy { OkHttpClient() }
val inputMethodManager: InputMethodManager by lazy {
application.getSystemServiceCompat(InputMethodManager::class.java)
}
val mainExecutor: Executor by lazy { application.mainExecutorCompat }
val notificationManager: NotificationManagerCompat by lazy {
NotificationManagerCompat.from(application)
}
val packageManager: PackageManager by lazy { application.packageManager }
val powerManager: PowerManager by lazy {
application.getSystemServiceCompat(PowerManager::class.java)
}
val storageManager: StorageManager by lazy {
application.getSystemServiceCompat(StorageManager::class.java)
}
val wifiManager: WifiManager by lazy {
application.getSystemServiceCompat(WifiManager::class.java)
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.content.Context
import android.content.pm.ApplicationInfo
import coil.key.Keyer
import coil.request.Options
import me.zhanghai.android.appiconloader.AppIconLoader
import me.zhanghai.android.files.R
import me.zhanghai.android.files.compat.longVersionCodeCompat
import me.zhanghai.android.files.util.getDimensionPixelSize
import java.io.Closeable
class AppIconApplicationInfoKeyer : Keyer<ApplicationInfo> {
override fun key(data: ApplicationInfo, options: Options): String =
AppIconLoader.getIconKey(data, data.longVersionCodeCompat, options.context)
}
class AppIconApplicationInfoFetcherFactory(
context: Context
) : AppIconFetcher.Factory<ApplicationInfo>(
// This is used by PrincipalListAdapter.
context.getDimensionPixelSize(R.dimen.icon_size), context
) {
override fun getApplicationInfo(data: ApplicationInfo): Pair<ApplicationInfo, Closeable?> =
data to null
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DataSource
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import me.zhanghai.android.appiconloader.AppIconLoader
import java.io.Closeable
class AppIconFetcher(
private val options: Options,
private val appIconLoader: AppIconLoader,
private val getApplicationInfo: () -> Pair<ApplicationInfo, Closeable?>
) : Fetcher {
override suspend fun fetch(): FetchResult {
val (applicationInfo, closeable) = getApplicationInfo()
val icon = closeable.use { appIconLoader.loadIcon(applicationInfo) }
// Not sampled because we only load with one fixed size.
return DrawableResult(icon.toDrawable(options.context.resources), false, DataSource.DISK)
}
abstract class Factory<T : Any>(
iconSize: Int,
context: Context,
shrinkNonAdaptiveIcons: Boolean = false
) : Fetcher.Factory<T> {
private val appIconLoader =
AppIconLoader(iconSize, shrinkNonAdaptiveIcons, context)
override fun create(data: T, options: Options, imageLoader: ImageLoader): Fetcher =
AppIconFetcher(options, appIconLoader) { getApplicationInfo(data) }
abstract fun getApplicationInfo(data: T): Pair<ApplicationInfo, Closeable?>
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.content.Context
import android.content.pm.ApplicationInfo
import coil.key.Keyer
import coil.request.Options
import me.zhanghai.android.files.R
import me.zhanghai.android.files.compat.PackageManagerCompat
import me.zhanghai.android.files.util.getDimensionPixelSize
import java.io.Closeable
data class AppIconPackageName(val packageName: String)
class AppIconPackageNameKeyer : Keyer<AppIconPackageName> {
override fun key(data: AppIconPackageName, options: Options): String = data.packageName
}
class AppIconPackageNameFetcherFactory(
private val context: Context
) : AppIconFetcher.Factory<AppIconPackageName>(
// This is used by FileListAdapter, and shrinking non-adaptive icons makes it look better as a
// badge.
context.getDimensionPixelSize(R.dimen.badge_size_plus_1dp), context, true
) {
override fun getApplicationInfo(data: AppIconPackageName): Pair<ApplicationInfo, Closeable?> {
// PackageManager.MATCH_UNINSTALLED_PACKAGES allows using PackageManager.MATCH_ANY_USER
// without the INTERACT_ACROSS_USERS permission when we are in the system user and it has a
// managed profile. It may also help corner cases like when the package is hidden.
return context.packageManager.getApplicationInfo(
data.packageName, PackageManagerCompat.MATCH_UNINSTALLED_PACKAGES
) to null
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright (c) 2021 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
fun ImageRequest.Builder.fadeIn(durationMillis: Int): ImageRequest.Builder =
apply {
placeholder(android.R.color.transparent)
transitionFactory(CrossfadeTransition.Factory(durationMillis, true))
}

View File

@ -1,37 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.os.Build
import coil.Coil
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import me.zhanghai.android.files.app.application
fun initializeCoil() {
Coil.setImageLoader(
ImageLoader.Builder(application)
.components {
add(AppIconApplicationInfoKeyer())
add(AppIconApplicationInfoFetcherFactory(application))
add(AppIconPackageNameKeyer())
add(AppIconPackageNameFetcherFactory(application))
add(PathAttributesKeyer())
add(PathAttributesFetcher.Factory(application))
add(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoderDecoder.Factory()
} else {
GifDecoder.Factory()
}
)
add(SvgDecoder.Factory(false))
}
.build()
)
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) 2022 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.graphics.Bitmap
import android.os.Build
import coil.decode.DataSource
import coil.size.Dimension
import coil.size.Scale
import coil.size.Size
import coil.size.isOriginal
import coil.size.pxOrElse
import java8.nio.file.Path
import me.zhanghai.android.files.filelist.isRemotePath
val Bitmap.Config.isHardware: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.HARDWARE
fun Bitmap.Config.toSoftware(): Bitmap.Config = if (isHardware) Bitmap.Config.ARGB_8888 else this
val Path.dataSource: DataSource
get() = if (isRemotePath) DataSource.NETWORK else DataSource.DISK
inline fun Size.widthPx(scale: Scale, original: () -> Int): Int =
if (isOriginal) original() else width.toPx(scale)
inline fun Size.heightPx(scale: Scale, original: () -> Int): Int =
if (isOriginal) original() else height.toPx(scale)
fun Dimension.toPx(scale: Scale) =
pxOrElse {
when (scale) {
Scale.FILL -> Int.MIN_VALUE
Scale.FIT -> Int.MAX_VALUE
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.graphics.drawable.Drawable
import coil.request.ImageRequest
import coil.target.ImageViewTarget
// Setting the placeholder drawable as error drawable again causes animation glitches, so we just
// ignore the onError() callback.
fun ImageRequest.Builder.ignoreError() {
val view = (build().target as ImageViewTarget).view
target(object : ImageViewTarget(view) {
override fun onError(error: Drawable?) {}
})
}

View File

@ -1,203 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.content.Context
import android.content.pm.ApplicationInfo
import android.media.MediaMetadataRetriever
import android.os.ParcelFileDescriptor
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.ImageSource
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.key.Keyer
import coil.request.Options
import coil.size.Dimension
import java8.nio.file.Path
import java8.nio.file.attribute.BasicFileAttributes
import me.zhanghai.android.files.R
import me.zhanghai.android.files.compat.use
import me.zhanghai.android.files.file.MimeType
import me.zhanghai.android.files.file.asMimeType
import me.zhanghai.android.files.file.isApk
import me.zhanghai.android.files.file.isImage
import me.zhanghai.android.files.file.isMedia
import me.zhanghai.android.files.file.isPdf
import me.zhanghai.android.files.file.isVideo
import me.zhanghai.android.files.file.lastModifiedInstant
import me.zhanghai.android.files.filelist.isRemotePath
import me.zhanghai.android.files.provider.common.AndroidFileTypeDetector
import me.zhanghai.android.files.provider.common.newInputStream
import me.zhanghai.android.files.provider.content.resolver.ResolverException
import me.zhanghai.android.files.provider.document.documentSupportsThumbnail
import me.zhanghai.android.files.provider.document.isDocumentPath
import me.zhanghai.android.files.provider.document.resolver.DocumentResolver
import me.zhanghai.android.files.provider.ftp.isFtpPath
import me.zhanghai.android.files.provider.linux.isLinuxPath
import me.zhanghai.android.files.settings.Settings
import me.zhanghai.android.files.util.getDimensionPixelSize
import me.zhanghai.android.files.util.getPackageArchiveInfoCompat
import me.zhanghai.android.files.util.isGetPackageArchiveInfoCompatible
import me.zhanghai.android.files.util.isMediaMetadataRetrieverCompatible
import me.zhanghai.android.files.util.runWithCancellationSignal
import me.zhanghai.android.files.util.setDataSource
import me.zhanghai.android.files.util.valueCompat
import okio.buffer
import okio.source
import java.io.Closeable
import java.io.IOException
import me.zhanghai.android.files.util.setDataSource as appSetDataSource
class PathAttributesKeyer : Keyer<Pair<Path, BasicFileAttributes>> {
override fun key(data: Pair<Path, BasicFileAttributes>, options: Options): String {
val (path, attributes) = data
return "$path:${attributes.lastModifiedInstant.toEpochMilli()}"
}
}
class PathAttributesFetcher(
private val data: Pair<Path, BasicFileAttributes>,
private val options: Options,
private val imageLoader: ImageLoader,
private val appIconFetcherFactory: AppIconFetcher.Factory<Path>,
private val videoFrameFetcherFactory: VideoFrameFetcher.Factory<Path>,
private val pdfPageFetcherFactory: PdfPageFetcher.Factory<Path>
) : Fetcher {
override suspend fun fetch(): FetchResult? {
val (path, attributes) = data
val (width, height) = options.size
// @see android.provider.MediaStore.ThumbnailConstants.MINI_SIZE
val isThumbnail = width is Dimension.Pixels && width.px <= 512
&& height is Dimension.Pixels && height.px <= 384
if (isThumbnail) {
width as Dimension.Pixels
height as Dimension.Pixels
if (path.isDocumentPath && attributes.documentSupportsThumbnail) {
val thumbnail = runWithCancellationSignal { signal ->
try {
DocumentResolver.getThumbnail(
path as DocumentResolver.Path, width.px, height.px, signal
)
} catch (e: ResolverException) {
e.printStackTrace()
null
}
}
if (thumbnail != null) {
return DrawableResult(
thumbnail.toDrawable(options.context.resources), true, path.dataSource
)
}
}
if (path.isRemotePath) {
// FTP doesn't support random access and requires one connection per parallel read.
val shouldReadRemotePath = !path.isFtpPath
&& Settings.READ_REMOTE_FILES_FOR_THUMBNAIL.valueCompat
if (!shouldReadRemotePath) {
error("Cannot read $path for thumbnail")
}
}
}
val mimeType = AndroidFileTypeDetector.getMimeType(data.first, data.second).asMimeType()
when {
mimeType.isApk && path.isGetPackageArchiveInfoCompatible -> {
try {
return appIconFetcherFactory.create(path, options, imageLoader).fetch()
} catch (e: Exception) {
e.printStackTrace()
}
}
mimeType.isImage || mimeType == MimeType.GENERIC -> {
val inputStream = path.newInputStream()
return SourceResult(
ImageSource(inputStream.source().buffer(), options.context),
if (mimeType != MimeType.GENERIC) mimeType.value else null, path.dataSource
)
}
mimeType.isMedia && path.isMediaMetadataRetrieverCompatible -> {
val embeddedPicture = try {
MediaMetadataRetriever().use { retriever ->
retriever.setDataSource(path)
retriever.embeddedPicture
}
} catch (e: Exception) {
e.printStackTrace()
null
}
if (embeddedPicture != null) {
return SourceResult(
ImageSource(
embeddedPicture.inputStream().source().buffer(), options.context
), null, path.dataSource
)
}
if (mimeType.isVideo) {
try {
return videoFrameFetcherFactory.create(path, options, imageLoader).fetch()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
mimeType.isPdf && (path.isLinuxPath || path.isDocumentPath) -> {
try {
return pdfPageFetcherFactory.create(path, options, imageLoader).fetch()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
return null
}
class Factory(private val context: Context) : Fetcher.Factory<Pair<Path, BasicFileAttributes>> {
private val appIconFetcherFactory = object : AppIconFetcher.Factory<Path>(
// This is used by FileListAdapter.
context.getDimensionPixelSize(R.dimen.large_icon_size), context
) {
override fun getApplicationInfo(data: Path): Pair<ApplicationInfo, Closeable?> {
val (packageInfo, closeable) =
context.packageManager.getPackageArchiveInfoCompat(data, 0)
val applicationInfo = packageInfo?.applicationInfo
if (applicationInfo == null) {
closeable?.close()
throw IOException("ApplicationInfo is null")
}
return applicationInfo to closeable
}
}
private val videoFrameFetcherFactory = object : VideoFrameFetcher.Factory<Path>() {
override fun MediaMetadataRetriever.setDataSource(data: Path) {
appSetDataSource(data)
}
}
private val pdfPageFetcherFactory = object : PdfPageFetcher.Factory<Path>() {
override fun openParcelFileDescriptor(data: Path): ParcelFileDescriptor =
when {
data.isLinuxPath ->
ParcelFileDescriptor.open(data.toFile(), ParcelFileDescriptor.MODE_READ_ONLY)
data.isDocumentPath ->
DocumentResolver.openParcelFileDescriptor(data as DocumentResolver.Path, "r")
else -> throw IllegalArgumentException(data.toString())
}
}
override fun create(
data: Pair<Path, BasicFileAttributes>,
options: Options,
imageLoader: ImageLoader
): Fetcher =
PathAttributesFetcher(
data, options, imageLoader, appIconFetcherFactory, videoFrameFetcherFactory,
pdfPageFetcherFactory
)
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.graphics.Color
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import androidx.annotation.ColorInt
import androidx.annotation.IntRange
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.DecodeUtils
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.ImageRequest
import coil.request.Options
import coil.request.Parameters
import kotlin.math.roundToInt
fun ImageRequest.Builder.pdfBackgroundColor(@ColorInt backgroundColor: Int): ImageRequest.Builder =
setParameter(PdfPageFetcher.PDF_BACKGROUND_COLOR_KEY, backgroundColor)
fun ImageRequest.Builder.pdfPageIndex(@IntRange(from = 0) pageIndex: Int): ImageRequest.Builder {
require(pageIndex >= 0) { "pageIndex must be >= 0." }
return setParameter(PdfPageFetcher.PDF_PAGE_INDEX_KEY, pageIndex)
}
@ColorInt
fun Parameters.pdfBackgroundColor(): Int? = value(PdfPageFetcher.PDF_BACKGROUND_COLOR_KEY) as Int?
@IntRange(from = 0)
fun Parameters.pdfPageIndex(): Int? = value(PdfPageFetcher.PDF_PAGE_INDEX_KEY) as Int?
class PdfPageFetcher(
private val options: Options,
private val openParcelFileDescriptor: () -> ParcelFileDescriptor
) : Fetcher {
override suspend fun fetch(): FetchResult =
openParcelFileDescriptor().use { pfd ->
PdfRenderer(pfd).use { renderer ->
val pageIndex = options.parameters.pdfPageIndex() ?: 0
renderer.openPage(pageIndex).use { page ->
val srcWidth = page.width
check(srcWidth > 0) {
"PDF page $pageIndex width $srcWidth isn't greater than 0"
}
val srcHeight = page.height
check(srcWidth > 0) {
"PDF page $pageIndex height $srcHeight isn't greater than 0"
}
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
val rawScale = DecodeUtils.computeSizeMultiplier(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale
)
val scale = if (options.allowInexactSize) {
rawScale.coerceAtMost(1.0)
} else {
rawScale
}
val width = (scale * srcWidth).roundToInt()
val height = (scale * srcHeight).roundToInt()
val config = options.config.toSoftware()
val bitmap = createBitmap(width, height, config)
val backgroundColor = options.parameters.pdfBackgroundColor() ?: Color.WHITE
bitmap.eraseColor(backgroundColor)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
DrawableResult(
drawable = bitmap.toDrawable(options.context.resources),
isSampled = scale < 1.0,
dataSource = DataSource.DISK
)
}
}
}
companion object {
const val PDF_BACKGROUND_COLOR_KEY = "coil#pdf_background_color"
const val PDF_PAGE_INDEX_KEY = "coil#pdf_page_index"
}
abstract class Factory<T : Any> : Fetcher.Factory<T> {
override fun create(data: T, options: Options, imageLoader: ImageLoader): Fetcher =
PdfPageFetcher(options) { openParcelFileDescriptor(data) }
protected abstract fun openParcelFileDescriptor(data: T): ParcelFileDescriptor
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright (c) 2022 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.coil
import android.graphics.Paint
import android.media.MediaMetadataRetriever
import android.os.Build
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.DecodeUtils
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import coil.request.videoFrameOption
import coil.request.videoFramePercent
import me.zhanghai.android.files.compat.getFrameAtTimeCompat
import me.zhanghai.android.files.compat.getScaledFrameAtTimeCompat
import me.zhanghai.android.files.compat.use
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
import kotlin.math.roundToLong
class VideoFrameFetcher(
private val options: Options,
private val setDataSource: MediaMetadataRetriever.() -> Unit
) : Fetcher {
override suspend fun fetch(): FetchResult =
MediaMetadataRetriever().use { retriever ->
retriever.setDataSource()
val rotation =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
?.toIntOrNull() ?: 0
var srcWidth: Int
var srcHeight: Int
when (rotation) {
90, 270 -> {
srcWidth =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
?.toIntOrNull() ?: 0
srcHeight =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
?.toIntOrNull() ?: 0
}
else -> {
srcWidth =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
?.toIntOrNull() ?: 0
srcHeight =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
?.toIntOrNull() ?: 0
}
}
val durationMillis =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
?.toLongOrNull() ?: 0L
// 1/3 is the first percentage tried by totem-video-thumbnailer.
// @see https://gitlab.gnome.org/GNOME/totem/-/blob/master/src/totem-video-thumbnailer.c#L543
val framePercent = options.parameters.videoFramePercent() ?: (1.0 / 3.0)
val frameMicros = TimeUnit.MICROSECONDS.convert(
(framePercent * durationMillis).roundToLong(), TimeUnit.MILLISECONDS
)
val frameOption = options.parameters.videoFrameOption()
?: MediaMetadataRetriever.OPTION_CLOSEST_SYNC
val bitmapParams = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
MediaMetadataRetriever.BitmapParams().apply { preferredConfig = options.config }
} else {
null
}
val outBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
&& srcWidth > 0 && srcHeight > 0) {
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
val rawScale = DecodeUtils.computeSizeMultiplier(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale
)
val scale = if (options.allowInexactSize) {
rawScale.coerceAtMost(1.0)
} else {
rawScale
}
val width = (scale * srcWidth).roundToInt()
val height = (scale * srcHeight).roundToInt()
retriever.getScaledFrameAtTimeCompat(
frameMicros, frameOption, width, height, bitmapParams
)
} else {
retriever.getFrameAtTimeCompat(frameMicros, frameOption, bitmapParams)?.also {
srcWidth = it.width
srcHeight = it.height
}
}
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
val rawScale = DecodeUtils.computeSizeMultiplier(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale
)
checkNotNull(outBitmap) { "Failed to decode frame at $frameMicros microseconds" }
val scale = if (options.allowInexactSize) {
rawScale.coerceAtMost(1.0)
} else {
rawScale
}
val width = (scale * srcWidth).roundToInt()
val height = (scale * srcHeight).roundToInt()
val isValidSize = if (options.allowInexactSize) {
outBitmap.width <= width && outBitmap.height <= height
} else {
outBitmap.width == width && outBitmap.height == height
}
val isValidConfig = !outBitmap.config.isHardware || options.config.isHardware
val bitmap = if (isValidSize && isValidConfig) {
outBitmap
} else {
val config = options.config.toSoftware()
createBitmap(width, height, config).applyCanvas {
scale(scale.toFloat(), scale.toFloat())
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
drawBitmap(outBitmap, 0f, 0f, paint)
outBitmap.recycle()
}
}
DrawableResult(
drawable = bitmap.toDrawable(options.context.resources),
isSampled = scale < 1.0,
dataSource = DataSource.DISK
)
}
abstract class Factory<T : Any> : Fetcher.Factory<T> {
override fun create(data: T, options: Options, imageLoader: ImageLoader): Fetcher =
VideoFrameFetcher(options) { setDataSource(data) }
protected abstract fun MediaMetadataRetriever.setDataSource(data: T)
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.colorpicker
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes
import androidx.preference.DialogPreference
import androidx.preference.PreferenceViewHolder
import me.zhanghai.android.files.R
import me.zhanghai.android.files.util.getFloatByAttr
import kotlin.math.roundToInt
abstract class BaseColorPreference : DialogPreference {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : super(
context, attrs, defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
@AttrRes defStyleAttr: Int,
@StyleRes defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
widgetLayoutResource = R.layout.color_preference_widget
dialogLayoutResource = R.layout.color_picker_dialog
setPositiveButtonText(android.R.string.ok)
setNegativeButtonText(android.R.string.cancel)
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val swatchView = holder.findViewById(R.id.swatch)
if (swatchView != null) {
val swatchDrawable = swatchView.background as GradientDrawable
swatchDrawable.setColor(value)
var alpha = 0xFF
if (!isEnabled) {
val disabledAlpha = context.getFloatByAttr(android.R.attr.disabledAlpha)
alpha = (disabledAlpha * alpha).roundToInt()
}
swatchDrawable.alpha = alpha
}
}
@get:ColorInt
abstract var value: Int
@get:ColorInt
abstract val defaultValue: Int
abstract val entryValues: IntArray
}

View File

@ -1,33 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.colorpicker
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.BaseAdapter
class ColorPaletteAdapter(private val colors: IntArray) : BaseAdapter() {
override fun hasStableIds(): Boolean = true
override fun getCount(): Int = colors.size
override fun getItem(position: Int): Int = colors[position]
override fun getItemId(position: Int): Long = getItem(position).toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val swatchView = convertView as ColorSwatchView?
?: ColorSwatchView(parent.context).apply {
layoutParams = AbsListView.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
}
return swatchView.apply {
setColor(getItem(position))
}
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.colorpicker
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.GridView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat
import kotlinx.parcelize.Parcelize
import me.zhanghai.android.files.R
import me.zhanghai.android.files.ui.MaterialPreferenceDialogFragmentCompat
import me.zhanghai.android.files.util.ParcelableState
import me.zhanghai.android.files.util.getState
import me.zhanghai.android.files.util.putState
import me.zhanghai.android.files.util.withTheme
class ColorPreferenceDialogFragment : MaterialPreferenceDialogFragmentCompat() {
override val preference: BaseColorPreference
get() = super.preference as BaseColorPreference
private lateinit var colors: IntArray
private var checkedColor = 0
private var defaultColor = 0
private lateinit var paletteGrid: GridView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val preference = preference
colors = preference.entryValues
checkedColor = preference.value
defaultColor = preference.defaultValue
} else {
val state = savedInstanceState.getState<State>()
colors = state.colors
checkedColor = state.checkedColor
defaultColor = state.defaultColor
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val checkedPosition = paletteGrid.checkedItemPosition
val checkedColor = if (checkedPosition != -1) colors[checkedPosition] else checkedColor
outState.putState(State(colors, checkedColor, defaultColor))
}
override fun onCreateDialogView(context: Context): View? =
super.onCreateDialogView(context.withTheme(theme))
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
paletteGrid = ViewCompat.requireViewById(view, R.id.palette)
paletteGrid.adapter = ColorPaletteAdapter(colors)
val checkedPosition = colors.indexOf(checkedColor)
if (checkedPosition != -1) {
paletteGrid.setItemChecked(checkedPosition, true)
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
if (defaultColor in colors) {
builder.setNeutralButton(R.string.default_, null)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
(super.onCreateDialog(savedInstanceState) as AlertDialog).apply {
if (defaultColor in colors) {
// Override the listener here so that we won't close the dialog.
setOnShowListener {
getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
paletteGrid.setItemChecked(colors.indexOf(defaultColor), true)
}
}
}
}
override fun onDialogClosed(positiveResult: Boolean) {
if (!positiveResult) {
return
}
val checkedPosition = paletteGrid.checkedItemPosition
if (checkedPosition == -1) {
return
}
val checkedColor = colors[checkedPosition]
preference.value = checkedColor
}
@Parcelize
private class State(
val colors: IntArray,
val checkedColor: Int,
val defaultColor: Int
) : ParcelableState
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.colorpicker
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes
import androidx.appcompat.content.res.AppCompatResources
import me.zhanghai.android.files.R
import me.zhanghai.android.files.ui.CheckableView
class ColorSwatchView : CheckableView {
private val gradientDrawable: GradientDrawable
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : super(
context, attrs, defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
@AttrRes defStyleAttr: Int,
@StyleRes defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
val background = AppCompatResources.getDrawable(
context, R.drawable.color_swatch_view_background
) as LayerDrawable
gradientDrawable = background.getDrawable(0) as GradientDrawable
setBackground(background)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(
resolveSize(suggestedMinimumWidth, widthMeasureSpec),
resolveSize(suggestedMinimumHeight, heightMeasureSpec)
)
}
fun setColor(@ColorInt color: Int) {
gradientDrawable.apply {
mutate()
setColor(color)
}
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.app.Activity
import android.app.ActivityManager.TaskDescription
import android.graphics.Color
import android.os.Build
import androidx.annotation.StyleRes
import androidx.core.app.ActivityCompat
import me.zhanghai.android.files.util.getColorByAttr
fun Activity.recreateCompat() {
ActivityCompat.recreate(this)
}
fun Activity.setThemeCompat(@StyleRes resid: Int) {
setTheme(resid)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val surfaceColor = getColorByAttr(com.google.android.material.R.attr.colorSurface)
if (surfaceColor != 0 && Color.alpha(surfaceColor) == 0xFF) {
@Suppress("DEPRECATION")
setTaskDescription(TaskDescription(null, null, surfaceColor))
}
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.pm.ApplicationInfo
import android.os.Build
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedField
@RestrictedHiddenApi
private val versionCodeField by lazyReflectedField(ApplicationInfo::class.java, "versionCode")
@RestrictedHiddenApi
private val longVersionCodeField by lazyReflectedField(
ApplicationInfo::class.java, "longVersionCode"
)
val ApplicationInfo.longVersionCodeCompat: Long
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
longVersionCodeField.getLong(this)
} else {
versionCodeField.getInt(this).toLong()
}

View File

@ -1,83 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StyleRes
import androidx.annotation.StyleableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.TintTypedArray
import androidx.core.content.ContextCompat
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedMethod
import java.util.concurrent.Executor
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
fun Context.checkSelfPermissionCompat(permission: String): Int =
ContextCompat.checkSelfPermission(this, permission)
@ColorInt
fun Context.getColorCompat(@ColorRes id: Int): Int = getColorStateListCompat(id).defaultColor
fun Context.getColorStateListCompat(@ColorRes id: Int): ColorStateList =
AppCompatResources.getColorStateList(this, id)!!
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable =
AppCompatResources.getDrawable(this, id)!!
fun <T> Context.getSystemServiceCompat(serviceClass: Class<T>): T =
ContextCompat.getSystemService(this, serviceClass)!!
val Context.mainExecutorCompat: Executor
get() = ContextCompat.getMainExecutor(this)
@SuppressLint("RestrictedApi")
fun Context.obtainStyledAttributesCompat(
set: AttributeSet? = null,
@StyleableRes attrs: IntArray,
@AttrRes defStyleAttr: Int = 0,
@StyleRes defStyleRes: Int = 0
): TintTypedArray =
TintTypedArray.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes)
@OptIn(ExperimentalContracts::class)
@SuppressLint("RestrictedApi")
inline fun <R> TintTypedArray.use(block: (TintTypedArray) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
block(this)
} finally {
recycle()
}
}
fun Context.registerReceiverCompat(
receiver: BroadcastReceiver?,
filter: IntentFilter,
flags: Int
): Intent? = ContextCompat.registerReceiver(this, receiver, filter, flags)
@RestrictedHiddenApi
private val getThemeResIdMethod by lazyReflectedMethod(Context::class.java, "getThemeResId")
val Context.themeResIdCompat: Int
@StyleRes
get() = getThemeResIdMethod.invoke(this) as Int

View File

@ -1,12 +0,0 @@
package me.zhanghai.android.files.compat
import org.threeten.bp.DateTimeUtils
import org.threeten.bp.Instant
import java.util.Calendar
import java.util.Date
fun Calendar.toInstantCompat(): Instant = DateTimeUtils.toInstant(this)
fun Date.toInstantCompat(): Instant = DateTimeUtils.toInstant(this)
fun Instant.toDateCompat(): Date = DateTimeUtils.toDate(this)

View File

@ -1,15 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.app.Dialog
import android.view.View
import androidx.annotation.IdRes
import androidx.core.app.DialogCompat
@Suppress("UNCHECKED_CAST")
fun <T : View> Dialog.requireViewByIdCompat(@IdRes id: Int): T =
DialogCompat.requireViewById(this, id) as T

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.text.method.DigitsKeyListener
import java.util.Locale
object DigitsKeyListenerCompat {
fun getInstance(locale: Locale?, sign: Boolean, decimal: Boolean): DigitsKeyListener =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
DigitsKeyListener.getInstance(locale, sign, decimal)
} else {
@Suppress("DEPRECATION")
DigitsKeyListener.getInstance(sign, decimal)
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import me.zhanghai.android.files.app.packageManager
object DocumentsContractCompat {
const val EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI"
const val EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED"
const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
const val EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary"
private const val PATH_DOCUMENT = "document"
private const val PATH_CHILDREN = "children"
private const val PATH_TREE = "tree"
/** @see DocumentsContract.PACKAGE_DOCUMENTS_UI */
fun getDocumentsUiPackage(): String? {
// See android.permission.cts.ProviderPermissionTest.testManageDocuments()
val packageInfos = packageManager.getPackagesHoldingPermissions(
arrayOf(android.Manifest.permission.MANAGE_DOCUMENTS), 0
)
val packageInfo = packageInfos.firstOrNull { it.packageName.endsWith(".documentsui") }
?: packageInfos.firstOrNull()
return packageInfo?.packageName
}
/** @see DocumentsContract.isDocumentUri */
fun isDocumentUri(uri: Uri): Boolean {
if (uri.scheme != ContentResolver.SCHEME_CONTENT) {
return false
}
val pathSegments = uri.pathSegments
return when (pathSegments.size) {
2 -> pathSegments[0] == PATH_DOCUMENT
4 -> pathSegments[0] == PATH_TREE && pathSegments[2] == PATH_DOCUMENT
else -> false
}
}
fun isTreeUri(uri: Uri): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
DocumentsContract.isTreeUri(uri)
} else {
uri.pathSegments.let { it.size >= 2 && it[0] == PATH_TREE }
}
fun isChildDocumentsUri(uri: Uri): Boolean {
val pathSegments = uri.pathSegments
return when (pathSegments.size) {
3 -> pathSegments[0] == PATH_DOCUMENT && pathSegments[2] == PATH_CHILDREN
5 ->
pathSegments[0] == PATH_TREE && pathSegments[2] == PATH_DOCUMENT
&& pathSegments[4] == PATH_CHILDREN
else -> false
}
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import androidx.core.graphics.drawable.DrawableCompat
var Drawable.layoutDirectionCompat: Int
get() = DrawableCompat.getLayoutDirection(this)
set(value) {
DrawableCompat.setLayoutDirection(this, value)
}
fun Drawable.setTintCompat(@ColorInt tint: Int) {
DrawableCompat.setTint(this, tint)
}

View File

@ -1,13 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
object EnvironmentCompat2 {
/**
* @see android.os.Environment.DIRECTORY_SCREENSHOTS
*/
const val DIRECTORY_SCREENSHOTS = "Screenshots"
}

View File

@ -1,16 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.system.ErrnoException
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedField
@RestrictedHiddenApi
private val functionNameField by lazyReflectedField(ErrnoException::class.java, "functionName")
val ErrnoException.functionNameCompat: String
get() = functionNameField.get(this) as String

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2024 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import java.io.IOException
import java.io.InputStream
import kotlin.reflect.KClass
fun KClass<InputStream>.nullInputStream(): InputStream =
object : InputStream() {
private var closed = false
override fun read(): Int {
ensureOpen()
return -1
}
override fun read(bytes: ByteArray, offset: Int, length: Int): Int {
if (!(offset >= 0 && length >= 0 && length <= bytes.size - offset)) {
throw IndexOutOfBoundsException()
}
ensureOpen()
return if (length == 0) 0 else -1
}
override fun skip(length: Long): Long {
ensureOpen()
return 0
}
override fun available(): Int {
ensureOpen()
return 0
}
override fun close() {
closed = true
}
private fun ensureOpen() {
if (closed) {
throw IOException("Stream closed")
}
}
}

View File

@ -1,17 +0,0 @@
/*
* Copyright (c) 2021 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.Intent
import me.zhanghai.android.files.util.andInv
fun Intent.removeFlagsCompat(flags: Int) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
removeFlags(flags)
} else {
setFlags(this.flags andInv flags)
}
}

View File

@ -1,10 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import kotlin.comparisons.reversed as kotlinReversed
fun <T> Comparator<T>.reversedCompat(): Comparator<T> = kotlinReversed()

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.icu.text.ListFormatter
import android.os.Build
object ListFormatterCompat {
fun format(vararg items: Any?): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ListFormatter.getInstance().format(*items)
} else {
formatCompat(items.asList())
}
fun format(items: Collection<*>): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ListFormatter.getInstance().format(items)
} else {
formatCompat(items)
}
private fun formatCompat(items: Collection<*>): String = items.joinToString(", ")
}

View File

@ -1,210 +0,0 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.app.LocaleConfig
import android.content.Context
import android.content.res.XmlResourceParser
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.annotation.XmlRes
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.LocaleListCompat
import org.xmlpull.v1.XmlPullParser
import java.io.FileNotFoundException
/**
* @see android.app.LocaleConfig
*/
class LocaleConfigCompat(context: Context) {
var status = 0
private set
var supportedLocales: LocaleListCompat? = null
private set
init {
val impl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Api33Impl(context)
} else {
Api21Impl(context)
}
status = impl.status
supportedLocales = impl.supportedLocales
}
companion object {
/**
* Succeeded reading the LocaleConfig structure stored in an XML file.
*/
const val STATUS_SUCCESS = 0
/**
* No android:localeConfig tag on <application>.
*/
const val STATUS_NOT_SPECIFIED = 1
/**
* Malformed input in the XML file where the LocaleConfig was stored.
*/
const val STATUS_PARSING_FAILED = 2
}
private abstract class Impl {
abstract val status: Int
abstract val supportedLocales: LocaleListCompat?
}
private class Api21Impl(context: Context) : Impl() {
override var status = 0
private set
override var supportedLocales: LocaleListCompat? = null
private set
init {
val resourceId = try {
getLocaleConfigResourceId(context)
} catch (e: Exception) {
Log.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e)
}
if (resourceId == ResourcesCompat.ID_NULL) {
status = STATUS_NOT_SPECIFIED
} else {
val resources = context.resources
try {
supportedLocales = resources.getXml(resourceId).use { parseLocaleConfig(it) }
status = STATUS_SUCCESS
} catch (e: Exception) {
val resourceEntryName = resources.getResourceEntryName(resourceId)
Log.w(TAG, "Failed to parse XML configuration from $resourceEntryName", e)
status = STATUS_PARSING_FAILED
}
}
}
// @see com.android.server.pm.pkg.parsing.ParsingPackageUtils
@XmlRes
private fun getLocaleConfigResourceId(context: Context): Int {
// Java cookies starts at 1, while passing 0 (invalid cookie for Java) makes
// AssetManager pick the last asset containing such a file name.
// We should go over all the assets containing AndroidManifest.xml, however there's no
// API to do that, so the best we can do is to start from the first asset and iterate
// until we can't find the next asset containing AndroidManifest.xml.
var cookie = 1
var isAndroidManifestFound = false
while (true) {
val parser = try {
context.assets.openXmlResourceParser(cookie, FILE_NAME_ANDROID_MANIFEST)
} catch (e: FileNotFoundException) {
if (!isAndroidManifestFound) {
++cookie
continue
} else {
break
}
}
isAndroidManifestFound = true
parser.use {
do {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name != TAG_MANIFEST) {
parser.skipCurrentTag()
continue
}
if (parser.getAttributeValue(null, ATTR_PACKAGE) != context.packageName) {
break
}
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name != TAG_APPLICATION) {
parser.skipCurrentTag()
continue
}
return parser.getAttributeResourceValue(
NAMESPACE_ANDROID, ATTR_LOCALE_CONFIG, ResourcesCompat.ID_NULL
)
}
} while (parser.next() != XmlPullParser.END_DOCUMENT)
}
++cookie
}
return ResourcesCompat.ID_NULL
}
private fun parseLocaleConfig(parser: XmlResourceParser): LocaleListCompat {
val localeNames = mutableSetOf<String>()
do {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name != TAG_LOCALE_CONFIG) {
parser.skipCurrentTag()
continue
}
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
if (parser.name != TAG_LOCALE) {
parser.skipCurrentTag()
continue
}
localeNames += parser.getAttributeValue(NAMESPACE_ANDROID, ATTR_NAME)
parser.skipCurrentTag()
}
} while (parser.next() != XmlPullParser.END_DOCUMENT)
return LocaleListCompat.forLanguageTags(localeNames.joinToString(","))
}
private fun XmlPullParser.skipCurrentTag() {
val outerDepth = depth
var type: Int
do {
type = next()
} while (type != XmlPullParser.END_DOCUMENT &&
(type != XmlPullParser.END_TAG || depth > outerDepth))
}
companion object {
private const val TAG = "LocaleConfigCompat"
private const val FILE_NAME_ANDROID_MANIFEST = "AndroidManifest.xml"
private const val TAG_APPLICATION = "application"
private const val TAG_LOCALE_CONFIG = "locale-config"
private const val TAG_LOCALE = "locale"
private const val TAG_MANIFEST = "manifest"
private const val NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"
private const val ATTR_LOCALE_CONFIG = "localeConfig"
private const val ATTR_NAME = "name"
private const val ATTR_PACKAGE = "package"
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class Api33Impl(context: Context) : Impl() {
override var status: Int = 0
private set
override var supportedLocales: LocaleListCompat? = null
private set
init {
val platformLocaleConfig = LocaleConfig(context)
status = platformLocaleConfig.status
supportedLocales = platformLocaleConfig.supportedLocales
?.let { LocaleListCompat.wrap(it) }
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.os.Build
import androidx.annotation.RequiresApi
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.KClass
val KClass<MediaMetadataRetriever>.METADATA_KEY_SAMPLERATE: Int
@RequiresApi(Build.VERSION_CODES.Q)
get() = 38
fun MediaMetadataRetriever.getFrameAtTimeCompat(
timeUs: Long,
option: Int,
params: MediaMetadataRetriever.BitmapParams?
): Bitmap? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && params != null) {
getFrameAtTime(timeUs, option, params)
} else {
getFrameAtTime(timeUs, option)
}
@RequiresApi(Build.VERSION_CODES.O_MR1)
fun MediaMetadataRetriever.getScaledFrameAtTimeCompat(
timeUs: Long,
option: Int,
dstWidth: Int,
dstHeight: Int,
params: MediaMetadataRetriever.BitmapParams?
): Bitmap? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && params != null) {
getScaledFrameAtTime(timeUs, option, dstWidth, dstHeight, params)
} else {
getScaledFrameAtTime(timeUs, option, dstWidth, dstHeight)
}
@OptIn(ExperimentalContracts::class)
inline fun <R> MediaMetadataRetriever.use(block: (MediaMetadataRetriever) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val autoCloseable: AutoCloseable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this
} else {
AutoCloseable { release() }
}
return autoCloseable.use { block(this) }
}

View File

@ -1,13 +0,0 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.view.Menu
import androidx.core.view.MenuCompat
fun Menu.setGroupDividerEnabledCompat(groupDividerEnabled: Boolean) {
MenuCompat.setGroupDividerEnabled(this, groupDividerEnabled)
}

View File

@ -1,41 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.system.OsConstants
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedMethod
import java.io.Closeable
import java.io.FileDescriptor
import java.nio.channels.FileChannel
object NioUtilsCompat {
@RestrictedHiddenApi
private val newFileChannelMethod by lazyReflectedMethod(
"java.nio.NioUtils", "newFileChannel", Closeable::class.java, FileDescriptor::class.java,
Int::class.java
)
@RestrictedHiddenApi
private val fileChannelImplOpenMethod by lazyReflectedMethod(
"sun.nio.ch.FileChannelImpl", "open", FileDescriptor::class.java, String::class.java,
Boolean::class.java, Boolean::class.java, Boolean::class.java, Any::class.java
)
fun newFileChannel(ioObject: Closeable, fd: FileDescriptor, flags: Int): FileChannel =
if (Build.VERSION.SDK_INT in Build.VERSION_CODES.N..<Build.VERSION_CODES.R) {
// They broke O_RDONLY by assuming it's non-zero, but in fact it is zero.
// https://android.googlesource.com/platform/libcore/+/nougat-release/luni/src/main/java/java/nio/NioUtils.java#63
val readable = flags and OsConstants.O_ACCMODE != OsConstants.O_WRONLY
val writable = flags and OsConstants.O_ACCMODE != OsConstants.O_RDONLY
val append = flags and OsConstants.O_APPEND == OsConstants.O_APPEND
fileChannelImplOpenMethod.invoke(
null, fd, null, readable, writable, append, ioObject
) as FileChannel
} else {
newFileChannelMethod.invoke(null, ioObject, fd, flags) as FileChannel
}
}

View File

@ -1,12 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat
val PackageInfo.longVersionCodeCompat: Long
get() = PackageInfoCompat.getLongVersionCode(this)

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2021 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.annotation.SuppressLint
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.SigningInfo
import android.os.Build
import me.zhanghai.android.files.util.andInv
import me.zhanghai.android.files.util.hasBits
object PackageManagerCompat {
@SuppressLint("InlinedApi")
const val MATCH_UNINSTALLED_PACKAGES = PackageManager.MATCH_UNINSTALLED_PACKAGES
}
fun PackageManager.getPackageArchiveInfoCompat(archiveFilePath: String, flags: Int): PackageInfo? {
var packageInfo = getPackageArchiveInfo(archiveFilePath, flags)
// getPackageArchiveInfo() returns null for unsigned APKs if signing info is requested.
if (packageInfo == null) {
val flagsWithoutGetSigningInfo = flags.andInv(
@Suppress("DEPRECATION")
PackageManager.GET_SIGNATURES or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
PackageManager.GET_SIGNING_CERTIFICATES
} else {
0
}
)
if (flags != flagsWithoutGetSigningInfo) {
packageInfo = getPackageArchiveInfo(archiveFilePath, flagsWithoutGetSigningInfo)
?.apply {
@Suppress("DEPRECATION")
if (flags.hasBits(PackageManager.GET_SIGNATURES)) {
signatures = emptyArray()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& flags.hasBits(PackageManager.GET_SIGNING_CERTIFICATES)) {
signingInfo = SigningInfo()
}
}
}
}
return packageInfo
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ParcelCompat
fun Parcel.readBooleanCompat(): Boolean = ParcelCompat.readBoolean(this)
fun Parcel.writeBooleanCompat(value: Boolean) {
ParcelCompat.writeBoolean(this, value)
}
fun <E : Parcelable?, L : MutableList<E>> Parcel.readParcelableListCompat(
list: L,
classLoader: ClassLoader?
): L {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@Suppress("UNCHECKED_CAST")
return readParcelableList(list, classLoader) as L
} else {
val size = readInt()
if (size == -1) {
list.clear()
return list
}
val listSize = list.size
for (index in 0..<size) {
@Suppress("UNCHECKED_CAST")
val element = readParcelable<E>(classLoader) as E
if (index < listSize) {
list[index] = element
} else {
list += element
}
}
if (size < listSize) {
list.subList(size, listSize).clear()
}
return list
}
}
fun <T : Parcelable?> Parcel.writeParcelableListCompat(value: List<T>?, flags: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
writeParcelableList(value, flags)
} else {
if (value == null) {
writeInt(-1)
return
}
writeInt(value.size)
for (element in value) {
writeParcelable(element, flags)
}
}
}
@Suppress("UNCHECKED_CAST")
fun <T> Parcel.readSerializableCompat(): T? = readSerializable() as T?

View File

@ -1,12 +0,0 @@
/*
* Copyright (c) 2021 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.pm.PermissionInfo
import androidx.core.content.pm.PermissionInfoCompat
val PermissionInfo.protectionCompat: Int
get() = PermissionInfoCompat.getProtection(this)

View File

@ -1,16 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.Context
object PreferenceManagerCompat {
fun getDefaultSharedPreferencesName(context: Context): String =
"${context.packageName}_preferences"
val defaultSharedPreferencesMode: Int
get() = Context.MODE_PRIVATE
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.os.ProxyFileDescriptorCallback
import android.system.ErrnoException
import android.system.OsConstants
import androidx.annotation.RequiresApi
abstract class ProxyFileDescriptorCallbackCompat {
@Throws(ErrnoException::class)
open fun onGetSize(): Long {
throw ErrnoException("onGetSize", OsConstants.EBADF)
}
@Throws(ErrnoException::class)
open fun onRead(offset: Long, size: Int, data: ByteArray): Int {
throw ErrnoException("onRead", OsConstants.EBADF)
}
@Throws(ErrnoException::class)
open fun onWrite(offset: Long, size: Int, data: ByteArray): Int {
throw ErrnoException("onWrite", OsConstants.EBADF)
}
@Throws(ErrnoException::class)
open fun onFsync() {
throw ErrnoException("onFsync", OsConstants.EINVAL)
}
abstract fun onRelease()
@RequiresApi(Build.VERSION_CODES.O)
fun toProxyFileDescriptorCallback(): ProxyFileDescriptorCallback {
return object : ProxyFileDescriptorCallback() {
@Throws(ErrnoException::class)
override fun onGetSize(): Long = this@ProxyFileDescriptorCallbackCompat.onGetSize()
@Throws(ErrnoException::class)
override fun onRead(offset: Long, size: Int, data: ByteArray): Int =
this@ProxyFileDescriptorCallbackCompat.onRead(offset, size, data)
@Throws(ErrnoException::class)
override fun onWrite(offset: Long, size: Int, data: ByteArray): Int =
this@ProxyFileDescriptorCallbackCompat.onWrite(offset, size, data)
@Throws(ErrnoException::class)
override fun onFsync() = this@ProxyFileDescriptorCallbackCompat.onFsync()
override fun onRelease() = this@ProxyFileDescriptorCallbackCompat.onRelease()
}
}
}

View File

@ -1,12 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.res.Resources
import androidx.annotation.DimenRes
import androidx.core.content.res.ResourcesCompat
fun Resources.getFloatCompat(@DimenRes id: Int) = ResourcesCompat.getFloat(this, id)

View File

@ -1,83 +0,0 @@
/*
* Copyright (c) 2022 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.graphics.drawable.RotateDrawable
import android.os.Build
import me.zhanghai.android.files.util.lazyReflectedField
import kotlin.reflect.KClass
fun KClass<RotateDrawable>.createCompat(): RotateDrawable =
RotateDrawable().apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
isPivotXRelativeCompat = true
pivotXCompat = 0.5f
isPivotYRelativeCompat = true
pivotYCompat = 0.5f
}
}
private val rotateDrawableMStateField by lazyReflectedField(RotateDrawable::class.java, "mState")
private val ROTATE_STATE_CLASS_NAME = "${RotateDrawable::class.java.name}\$RotateState"
private val rotateStateMPivotXRelField by lazyReflectedField(ROTATE_STATE_CLASS_NAME, "mPivotXRel")
var RotateDrawable.isPivotXRelativeCompat: Boolean
get() = isPivotXRelative
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
isPivotXRelative = value
} else {
if (isPivotXRelative != value) {
rotateStateMPivotXRelField.setBoolean(rotateDrawableMStateField.get(this), value)
invalidateSelf()
}
}
}
private val rotateStateMPivotXField by lazyReflectedField(ROTATE_STATE_CLASS_NAME, "mPivotX")
var RotateDrawable.pivotXCompat: Float
get() = pivotX
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
pivotX = value
} else {
if (pivotX != value) {
rotateStateMPivotXField.setFloat(rotateDrawableMStateField.get(this), value)
invalidateSelf()
}
}
}
private val rotateStateMPivotYRelField by lazyReflectedField(ROTATE_STATE_CLASS_NAME, "mPivotYRel")
var RotateDrawable.isPivotYRelativeCompat: Boolean
get() = isPivotYRelative
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
isPivotYRelative = value
} else {
if (isPivotYRelative != value) {
rotateStateMPivotYRelField.setBoolean(rotateDrawableMStateField.get(this), value)
invalidateSelf()
}
}
}
private val rotateStateMPivotYField by lazyReflectedField(ROTATE_STATE_CLASS_NAME, "mPivotY")
var RotateDrawable.pivotYCompat: Float
get() = pivotY
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
pivotY = value
} else {
if (pivotY != value) {
rotateStateMPivotYField.setFloat(rotateDrawableMStateField.get(this), value)
invalidateSelf()
}
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import androidx.annotation.RequiresApi
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedClass
import me.zhanghai.android.files.util.lazyReflectedMethod
import java.io.File
import java.io.FileDescriptor
/*
* @see android.os.SELinux
* @see <a href="https://android.googlesource.com/platform/frameworks/base/+/jb-mr1-release/core/java/android/os/SELinux.java">
* jb-mr1-release/SELinux.java</a>
* @see <a href="https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat/hiddenapi-light-greylist.txt">
* hiddenapi-light-greylist.txt</a>
*/
object SELinuxCompat {
private val seLinuxClass by lazyReflectedClass("android.os.SELinux")
private val isSELinuxEnabledMethod by lazyReflectedMethod(seLinuxClass, "isSELinuxEnabled")
private val isSELinuxEnforcedMethod by lazyReflectedMethod(seLinuxClass, "isSELinuxEnforced")
@RestrictedHiddenApi
private val setFSCreateContextMethod by lazyReflectedMethod(
seLinuxClass, "setFSCreateContext", String::class.java
)
@RestrictedHiddenApi
private val setFileContextMethod by lazyReflectedMethod(
seLinuxClass, "setFileContext", String::class.java, String::class.java
)
private val getFileContextStringMethod by lazyReflectedMethod(
seLinuxClass, "getFileContext", String::class.java
)
@RestrictedHiddenApi
private val getPeerContextMethod by lazyReflectedMethod(
seLinuxClass, "getPeerContext", FileDescriptor::class.java
)
@get:RequiresApi(Build.VERSION_CODES.Q)
@RestrictedHiddenApi
private val getFileContextFileDescriptorMethod by lazyReflectedMethod(
seLinuxClass, "getFileContext", FileDescriptor::class.java
)
private val getContextMethod by lazyReflectedMethod(seLinuxClass, "getContext")
private val getPidContextMethod by lazyReflectedMethod(
seLinuxClass, "getPidContext", Int::class.java
)
private val checkSELinuxAccessMethod by lazyReflectedMethod(
seLinuxClass, "checkSELinuxAccess", String::class.java, String::class.java,
String::class.java, String::class.java
)
@RestrictedHiddenApi
private val nativeRestoreconMethod by lazyReflectedMethod(
seLinuxClass, "native_restorecon", String::class.java, Int::class.java
)
@RestrictedHiddenApi
private val restoreconStringMethod by lazyReflectedMethod(
seLinuxClass, "restorecon", String::class.java
)
@RestrictedHiddenApi
private val restoreconFileMethod by lazyReflectedMethod(
seLinuxClass, "restorecon", File::class.java
)
private val restoreconRecursiveMethod by lazyReflectedMethod(
seLinuxClass, "restoreconRecursive", File::class.java
)
val isSELinuxEnabled: Boolean
get() = isSELinuxEnabledMethod.invoke(null) as Boolean
val isSELinuxEnforced: Boolean
get() = isSELinuxEnforcedMethod.invoke(null) as Boolean
fun setFSCreateContext(context: String?): Boolean =
setFSCreateContextMethod.invoke(null, context) as Boolean
fun setFileContext(path: String, context: String): Boolean =
setFileContextMethod.invoke(null, path, context) as Boolean
fun getFileContext(path: String): String? =
getFileContextStringMethod.invoke(null, path) as String?
fun getPeerContext(fd: FileDescriptor): String? =
getPeerContextMethod.invoke(null, fd) as String?
@RequiresApi(Build.VERSION_CODES.Q)
fun getFileContext(fd: FileDescriptor): String? =
getFileContextFileDescriptorMethod.invoke(null, fd) as String?
val context: String?
get() = getContextMethod.invoke(null) as String?
fun getPidContext(pid: Int): String? = getPidContextMethod.invoke(null, pid) as String?
fun checkSELinuxAccess(scon: String, tcon: String, tclass: String, perm: String): Boolean =
checkSELinuxAccessMethod.invoke(null, scon, tcon, tclass, perm) as Boolean
fun native_restorecon(pathname: String?, flags: Int): Boolean =
nativeRestoreconMethod.invoke(null, pathname, flags) as Boolean
fun restorecon(pathname: String): Boolean =
restoreconStringMethod.invoke(null, pathname) as Boolean
fun restorecon(file: File): Boolean = restoreconFileMethod.invoke(null, file) as Boolean
fun restoreconRecursive(file: File): Boolean =
restoreconRecursiveMethod.invoke(null, file) as Boolean
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import androidx.annotation.RequiresApi
import java8.nio.channels.SeekableByteChannel
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.channels.SeekableByteChannel as JavaSeekableByteChannel
@RequiresApi(Build.VERSION_CODES.N)
fun SeekableByteChannel.toJavaSeekableByteChannel(): JavaSeekableByteChannel =
// java8.nio.channels.FileChannel extends from java.nio.channels.FileChannel, so in that case
// the current object may already be implementing java.nio.channels.SeekableByteChannel.
this as? JavaSeekableByteChannel ?: DelegateJavaSeekableByteChannel(this)
@RequiresApi(Build.VERSION_CODES.N)
private class DelegateJavaSeekableByteChannel(
private val channel: SeekableByteChannel
) : JavaSeekableByteChannel {
@Throws(IOException::class)
override fun read(dst: ByteBuffer): Int = channel.read(dst)
@Throws(IOException::class)
override fun write(src: ByteBuffer): Int = channel.write(src)
@Throws(IOException::class)
override fun position(): Long = channel.position()
@Throws(IOException::class)
override fun position(newPosition: Long): DelegateJavaSeekableByteChannel {
channel.position(newPosition)
return this
}
@Throws(IOException::class)
override fun size(): Long = channel.size()
@Throws(IOException::class)
override fun truncate(size: Long): DelegateJavaSeekableByteChannel {
channel.truncate(size)
return this
}
override fun isOpen(): Boolean = channel.isOpen
@Throws(IOException::class)
override fun close() {
channel.close()
}
}

View File

@ -1,13 +0,0 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.app.Service
import androidx.core.app.ServiceCompat
fun Service.stopForegroundCompat(flags: Int) {
ServiceCompat.stopForeground(this, flags)
}

View File

@ -1,118 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.os.Handler
import android.os.ParcelFileDescriptor
import android.os.storage.StorageManager
import android.os.storage.StorageVolume
import kotlinx.coroutines.runBlocking
import me.zhanghai.android.files.util.lazyReflectedMethod
import java.io.IOException
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
private val getVolumeListMethod by lazyReflectedMethod(StorageManager::class.java, "getVolumeList")
val StorageManager.storageVolumesCompat: List<StorageVolume>
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
storageVolumes
} else {
@Suppress("UNCHECKED_CAST")
(getVolumeListMethod.invoke(this) as Array<StorageVolume>).toList()
}
// Thanks to fython for https://gist.github.com/fython/924f8d9019bca75d22de116bb69a54a1
@Throws(IOException::class)
fun StorageManager.openProxyFileDescriptorCompat(
mode: Int,
callback: ProxyFileDescriptorCallbackCompat,
handler: Handler
): ParcelFileDescriptor =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
openProxyFileDescriptor(mode, callback.toProxyFileDescriptorCallback(), handler)
} else {
// TODO: Support other modes?
if (mode != ParcelFileDescriptor.MODE_READ_ONLY) {
throw UnsupportedOperationException("mode $mode")
}
val pfds = ParcelFileDescriptor.createReliablePipe()
PipeWriter(pfds[1], callback, handler).start()
pfds[0]
}
private class PipeWriter(
private val pfd: ParcelFileDescriptor,
private val callback: ProxyFileDescriptorCallbackCompat,
private val handler: Handler
) : Thread("StorageManagerCompat.PipeWriter-${id.getAndIncrement()}") {
override fun run() {
try {
ParcelFileDescriptor.AutoCloseOutputStream(pfd).use { outputStream ->
var offset = 0L
val buffer = ByteArray(4 * 1024)
while (true) {
val size = runBlocking {
callback.awaitOnRead(offset, buffer.size, buffer, handler)
}
if (size == 0) {
break
}
offset += size.toLong()
outputStream.write(buffer, 0, size)
}
runBlocking { callback.awaitOnRelease(handler) }
}
} catch (e: Exception) {
e.printStackTrace()
try {
pfd.closeWithError(e.message)
} catch (e2: IOException) {
e2.printStackTrace()
}
}
}
companion object {
private val id = AtomicInteger()
}
}
private suspend fun ProxyFileDescriptorCallbackCompat.awaitOnRead(
offset: Long,
size: Int,
data: ByteArray,
handler: Handler
): Int =
suspendCoroutine { continuation ->
handler.post {
val readSize = try {
onRead(offset, size, data)
} catch (t: Throwable) {
continuation.resumeWithException(t)
return@post
}
continuation.resume(readSize)
}
}
private suspend fun ProxyFileDescriptorCallbackCompat.awaitOnRelease(handler: Handler) {
suspendCoroutine<Unit> { continuation ->
handler.post {
try {
onRelease()
} catch (t: Throwable) {
continuation.resumeWithException(t)
return@post
}
continuation.resume(Unit)
}
}
}

View File

@ -1,82 +0,0 @@
/*
* Copyright (c) 2018 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.os.storage.StorageVolume
import android.provider.DocumentsContract
import me.zhanghai.android.files.util.lazyReflectedMethod
import java.io.File
// Work around @SuppressLint not applicable to top level property with delegate.
@SuppressLint("NewApi")
private val storageVolumeClass = StorageVolume::class.java
private val getPathMethod by lazyReflectedMethod(storageVolumeClass, "getPath")
val StorageVolume.pathCompat: String
get() = getPathMethod.invoke(this) as String
private val getPathFileMethod by lazyReflectedMethod(storageVolumeClass, "getPathFile")
val StorageVolume.pathFileCompat: File
get() = File(pathCompat)
val StorageVolume.directoryCompat: File?
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
directory
} else {
when (stateCompat) {
Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY -> pathFileCompat
else -> null
}
}
@SuppressLint("NewApi")
fun StorageVolume.getDescriptionCompat(context: Context): String = getDescription(context)
val StorageVolume.isPrimaryCompat: Boolean
@SuppressLint("NewApi")
get() = isPrimary
val StorageVolume.isRemovableCompat: Boolean
@SuppressLint("NewApi")
get() = isRemovable
val StorageVolume.isEmulatedCompat: Boolean
@SuppressLint("NewApi")
get() = isEmulated
val StorageVolume.uuidCompat: String?
@SuppressLint("NewApi")
get() = uuid
val StorageVolume.stateCompat: String
@SuppressLint("NewApi")
get() = state
fun StorageVolume.createOpenDocumentTreeIntentCompat(): Intent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
createOpenDocumentTreeIntent()
} else {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
val rootId = if (isEmulatedCompat) {
DocumentsContractCompat.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
} else {
uuidCompat
}
val rootUri = DocumentsContract.buildRootUri(
DocumentsContractCompat.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, rootId
)
putExtra(DocumentsContractCompat.EXTRA_INITIAL_URI, rootUri)
putExtra(DocumentsContractCompat.EXTRA_SHOW_ADVANCED, true)
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.os.Build
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.core.widget.TextViewCompat
import me.zhanghai.android.files.util.lazyReflectedMethod
private val isSingleLineMethod by lazyReflectedMethod(TextView::class.java, "isSingleLine")
val TextView.isSingleLineCompat: Boolean
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
isSingleLine
} else {
isSingleLineMethod.invoke(this) as Boolean
}
fun TextView.setTextAppearanceCompat(@StyleRes resId: Int) {
TextViewCompat.setTextAppearance(this, resId)
}

View File

@ -1,13 +0,0 @@
package me.zhanghai.android.files.compat
import android.os.Build
import kotlin.reflect.KClass
fun <T> KClass<ThreadLocal<*>>.withInitial(supplier: () -> T): ThreadLocal<T> =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ThreadLocal.withInitial(supplier)
} else {
object : ThreadLocal<T>() {
override fun initialValue(): T = supplier.invoke()
}
}

View File

@ -1,12 +0,0 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.util.TypedValue
import androidx.core.util.TypedValueCompat
val TypedValue.complexUnitCompat: Int
get() = TypedValueCompat.getUnitFromComplexDimension(data)

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.files.compat
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.view.View
import androidx.annotation.IdRes
import androidx.core.view.ViewCompat
import me.zhanghai.android.foregroundcompat.ForegroundCompat
@Suppress("UNCHECKED_CAST")
fun <T : View> View.requireViewByIdCompat(@IdRes id: Int): T =
ViewCompat.requireViewById(this, id) as T
var View.scrollIndicatorsCompat: Int
get() = ViewCompat.getScrollIndicators(this)
set(value) {
ViewCompat.setScrollIndicators(this, value)
}
fun View.setScrollIndicatorsCompat(indicators: Int, mask: Int) {
ViewCompat.setScrollIndicators(this, indicators, mask)
}
var View.foregroundCompat: Drawable?
get() = ForegroundCompat.getForeground(this)
set(value) {
ForegroundCompat.setForeground(this, value)
}
var View.foregroundGravityCompat: Int
get() = ForegroundCompat.getForegroundGravity(this)
set(value) {
ForegroundCompat.setForegroundGravity(this, value)
}
var View.foregroundTintListCompat: ColorStateList?
get() = ForegroundCompat.getForegroundTintList(this)
set(value) {
ForegroundCompat.setForegroundTintList(this, value)
}
var View.foregroundTintModeCompat: PorterDuff.Mode?
get() = ForegroundCompat.getForegroundTintMode(this)
set(value) {
ForegroundCompat.setForegroundTintMode(this, value)
}

Some files were not shown because too many files have changed in this diff Show More