├── .gitignore
├── LICENSE
├── README.md
├── assembly
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── complete
│ ├── AndroidManifest.xml
│ └── res
│ │ ├── values-zh
│ │ └── strings.xml
│ │ ├── values
│ │ └── strings.xml
│ │ └── xml
│ │ └── backup.xml
│ └── main
│ ├── AndroidManifest.xml
│ └── res
│ └── README
├── build.gradle
├── engine
├── build.gradle
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ └── api
│ │ └── ApiActivityTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ ├── AdbShell.kt
│ │ ├── AppUpdateReceiver.java
│ │ ├── InternalActivity.java
│ │ ├── IslandDeviceAdminReceiver.java
│ │ ├── api
│ │ ├── ApiActivity.java
│ │ ├── ApiDispatcher.java
│ │ └── ApiReceiver.java
│ │ ├── provisioning
│ │ ├── AutoIncrementalProvision.java
│ │ ├── CrossProfileIntentFiltersHelper.java
│ │ ├── IslandProvisioning.java
│ │ ├── ManualProvisioningReceiver.java
│ │ ├── ProfileOwnerManualProvisioning.java
│ │ └── task
│ │ │ ├── DeleteNonRequiredAppsTask.java
│ │ │ ├── ProvisionLogger.java
│ │ │ └── Utils.java
│ │ ├── service
│ │ └── IslandPersistentService.kt
│ │ ├── shuttle
│ │ └── ServiceShuttleActivity.java
│ │ └── util
│ │ ├── Cryptography.java
│ │ ├── Dump.kt
│ │ └── PackageManagerWrapper.java
│ └── res
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-v24
│ └── disallowed_apps_managed_profile.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ ├── values
│ ├── disallowed_apps_managed_device.xml
│ ├── disallowed_apps_managed_profile.xml
│ ├── disallowed_apps_managed_user.xml
│ ├── packages_to_delete_new_managed_profile.xml
│ ├── required_apps_managed_device.xml
│ ├── required_apps_managed_profile.xml
│ ├── required_apps_managed_user.xml
│ ├── strings.xml
│ ├── vendor_disallowed_apps_managed_device.xml
│ ├── vendor_disallowed_apps_managed_profile.xml
│ ├── vendor_disallowed_apps_managed_user.xml
│ ├── vendor_required_apps_managed_device.xml
│ ├── vendor_required_apps_managed_profile.xml
│ └── vendor_required_apps_managed_user.xml
│ └── xml
│ └── device_admin.xml
├── fileprovider
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── android
│ │ └── content
│ │ │ ├── ContentResolver.java
│ │ │ └── IContentProvider.java
│ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ └── fileprovider
│ │ ├── ContentResolverWrapper.java
│ │ ├── ExternalStorageProviderProxy.java
│ │ └── ShuttleProvider.java
│ └── res
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── installer
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ └── installer
│ │ ├── AppInfoForwarderActivity.kt
│ │ ├── AppInstallationNotifier.kt
│ │ ├── AppInstallerActivity.java
│ │ ├── AppInstallerStatusReceiver.kt
│ │ ├── AppInstallerUtils.kt
│ │ ├── AppSettingsHelperService.kt
│ │ └── analyzer
│ │ └── ApkAnalyzer.kt
│ └── res
│ ├── layout
│ └── dialog_checkbox.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ └── strings.xml
├── mobile
├── .gitignore
├── build.gradle
├── libs
│ └── setup-wizard-lib-platform-release.aar
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── oasisfeng
│ │ ├── common
│ │ └── app
│ │ │ ├── AppInfo.java
│ │ │ ├── AppLabelCache.java
│ │ │ ├── AppListProvider.java
│ │ │ ├── BaseAndroidViewModel.kt
│ │ │ ├── BaseAppListViewModel.java
│ │ │ └── BaseAppViewModel.java
│ │ ├── island
│ │ ├── MainActivity.java
│ │ ├── TempDebug.kt
│ │ ├── action
│ │ │ └── FeatureAction.kt
│ │ ├── adb
│ │ │ ├── AdbSecure.java
│ │ │ └── ProfileRestrictionsSync.java
│ │ ├── console
│ │ │ └── apps
│ │ │ │ └── AppListFragment.java
│ │ ├── controller
│ │ │ ├── IslandAppClones.kt
│ │ │ └── IslandAppControl.kt
│ │ ├── data
│ │ │ ├── IslandAppInfo.java
│ │ │ ├── IslandAppListProvider.kt
│ │ │ ├── LiveProfileState.kt
│ │ │ ├── LiveUserRestriction.java
│ │ │ └── helper
│ │ │ │ └── AppStateTrackingHelper.java
│ │ ├── engine
│ │ │ └── ClonedHiddenSystemApps.kt
│ │ ├── featured
│ │ │ ├── FeaturedListViewModel.java
│ │ │ └── FeaturedViewModel.java
│ │ ├── files
│ │ │ └── IslandFiles.java
│ │ ├── greenify
│ │ │ └── GreenifyClient.java
│ │ ├── guide
│ │ │ └── UserGuide.java
│ │ ├── model
│ │ │ ├── AppListViewModel.java
│ │ │ ├── AppViewModel.java
│ │ │ ├── IslandViewModel.kt
│ │ │ └── MainViewModel.kt
│ │ ├── security
│ │ │ └── SecurityPrompt.java
│ │ ├── settings
│ │ │ ├── AppOpsPermissionsUnlock.kt
│ │ │ ├── GeneralPreferenceFragment.kt
│ │ │ ├── IslandNameManager.kt
│ │ │ ├── IslandSettingsActivity.kt
│ │ │ ├── OpsManager.kt
│ │ │ ├── Preferences.kt
│ │ │ └── SettingsActivity.kt
│ │ ├── setup
│ │ │ ├── IslandSetup.java
│ │ │ ├── SetupActivity.java
│ │ │ ├── SetupViewModel.java
│ │ │ └── SetupWizardFragment.java
│ │ ├── shortcut
│ │ │ └── IslandAppShortcut.kt
│ │ ├── util
│ │ │ ├── FloatingActionButtonBehavior.java
│ │ │ ├── SimpleAsyncTask.java
│ │ │ └── TextFormat.java
│ │ └── widget
│ │ │ └── PersistableSearchView.java
│ │ ├── settings
│ │ ├── ActionButtonPreference.java
│ │ └── AdvancedPreference.java
│ │ └── ui
│ │ └── card
│ │ └── CardViewModel.java
│ └── res
│ ├── animator
│ ├── slide_back_in.xml
│ ├── slide_back_out.xml
│ ├── slide_next_in.xml
│ └── slide_next_out.xml
│ ├── color
│ └── chip_background_color.xml
│ ├── drawable-nodpi
│ ├── ic_launcher_appops.webp
│ ├── ic_launcher_fx.webp
│ ├── ic_launcher_greenify.webp
│ └── ic_launcher_saf_enhancer.webp
│ ├── drawable-xxxhdpi
│ └── setup_header.9.png
│ ├── drawable
│ ├── app_icon_empty.xml
│ ├── bottom_navigation_text_color.xml
│ ├── ic_add_to_photos_24dp.xml
│ ├── ic_explore_black_24dp.xml
│ ├── ic_filter_list_white_24dp.xml
│ ├── ic_info_black_24dp.xml
│ ├── ic_island_black_24dp.xml
│ ├── ic_launch_24dp.xml
│ ├── ic_lightbulb_outline_24dp.xml
│ ├── ic_lock_24dp.xml
│ ├── ic_mainland_black_24dp.xml
│ ├── ic_search_white_24dp.xml
│ ├── ic_settings_black_24dp.xml
│ └── ic_unlock_24dp.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── app_entry.xml
│ ├── app_list.xml
│ ├── card.xml
│ ├── featured_entry.xml
│ ├── preference_widget_action_button.xml
│ └── setup_wizard.xml
│ ├── menu
│ ├── app_actions.xml
│ ├── main_actions.xml
│ └── pref_island_actions.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_background.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_background.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_background.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_background.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_background.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── values-night
│ └── colors.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── constants.xml
│ ├── dimens.xml
│ ├── ids.xml
│ ├── preferences.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── actions.xml
│ ├── pref_about.xml
│ ├── pref_general.xml
│ ├── pref_headers.xml
│ ├── pref_island.xml
│ └── searchable.xml
├── open
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── android
│ │ ├── app
│ │ │ ├── DerivedAppOpsManager.java
│ │ │ └── admin
│ │ │ │ └── DerivedDevicePolicyManager.java
│ │ └── content
│ │ │ └── DerivedRestrictionsManager.java
│ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ ├── ApiConstants.java
│ │ ├── DelegatedScopeAuthorization.kt
│ │ ├── DelegationManager.kt
│ │ ├── RestrictedBinderProxy.java
│ │ ├── SystemServiceBridge.java
│ │ └── api
│ │ ├── DelegatedAppOpsManager.java
│ │ ├── DelegatedDevicePolicyManager.java
│ │ ├── DerivedManagerHelper.java
│ │ └── PermissionForwardingRestrictionsManager.java
│ └── res
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ └── strings.xml
├── settings.gradle
├── shared
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── com
│ │ └── oasisfeng
│ │ └── island
│ │ └── shuttle
│ │ ├── IMethodShuttle.aidl
│ │ ├── IServiceConnection.aidl
│ │ ├── IUnbinder.aidl
│ │ └── MethodInvocation.aidl
│ ├── java
│ ├── android
│ │ └── app
│ │ │ └── NotificationManagerExtender.java
│ └── com
│ │ └── oasisfeng
│ │ ├── android
│ │ └── content
│ │ │ ├── IntentCompat.java
│ │ │ └── pm
│ │ │ ├── ComponentInfo.kt
│ │ │ └── PackageManagerUtils.kt
│ │ ├── island
│ │ ├── Config.java
│ │ ├── IslandApplication.java
│ │ ├── PersistentService.kt
│ │ ├── analytics
│ │ │ ├── Analytics.kt
│ │ │ ├── AnalyticsImpl.kt
│ │ │ └── CrashReport.java
│ │ ├── api
│ │ │ └── Api.java
│ │ ├── appops
│ │ │ ├── AppOpsCompat.java
│ │ │ └── AppOpsHelper.kt
│ │ ├── data
│ │ │ └── helper
│ │ │ │ └── ApplicationInfoExtensions.kt
│ │ ├── engine
│ │ │ ├── CrossProfile.kt
│ │ │ ├── IslandManager.java
│ │ │ └── common
│ │ │ │ └── WellKnownPackages.java
│ │ ├── firebase
│ │ │ └── FirebaseWrapper.java
│ │ ├── installer
│ │ │ └── InstallerExtras.java
│ │ ├── notification
│ │ │ ├── NotificationIds.java
│ │ │ └── NotificationIds.kt
│ │ ├── provisioning
│ │ │ ├── CriticalAppsManager.java
│ │ │ └── SystemAppsManager.java
│ │ ├── settings
│ │ │ └── IslandSettings.kt
│ │ ├── shortcut
│ │ │ └── ShortcutIcons.java
│ │ ├── shuttle
│ │ │ ├── ActivityShuttle.java
│ │ │ ├── Closure.kt
│ │ │ ├── ContextShuttle.java
│ │ │ ├── MethodInvocation.java
│ │ │ ├── MethodShuttle.java
│ │ │ ├── ServiceShuttle.java
│ │ │ ├── ServiceShuttleContext.java
│ │ │ ├── Shuttle.kt
│ │ │ ├── ShuttleCarrierActivity.kt
│ │ │ ├── ShuttleProvider.kt
│ │ │ └── ShuttleServiceConnection.java
│ │ └── util
│ │ │ ├── CallerAwareActivity.java
│ │ │ ├── DeviceAdmins.java
│ │ │ ├── DevicePolicies.java
│ │ │ ├── DevicePolicies.kt
│ │ │ ├── Hacks.java
│ │ │ ├── Modules.java
│ │ │ ├── OwnerUser.java
│ │ │ ├── Permissions.java
│ │ │ ├── ProfileUser.java
│ │ │ ├── RomVariants.kt
│ │ │ ├── UserHandles.kt
│ │ │ └── Users.java
│ │ ├── perf
│ │ ├── Performances.java
│ │ ├── Stopwatch.java
│ │ └── Ticker.java
│ │ └── settings
│ │ ├── AppSettings.kt
│ │ └── AppSettingsProvider.kt
│ └── res
│ ├── drawable
│ ├── ic_landscape_black_24dp.xml
│ └── ic_settings_applications_white_24dp.xml
│ ├── values-night
│ └── colors.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-v24
│ └── flags.xml
│ ├── values-v26
│ └── flags.xml
│ ├── values-v28
│ └── flags.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── flags.xml
│ ├── settings.xml
│ └── strings.xml
│ └── xml
│ ├── analytics_tracker.xml
│ └── config_defaults.xml
└── watcher
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── oasisfeng
│ └── island
│ └── watcher
│ ├── IslandAppWatcher.kt
│ └── IslandWatcher.kt
└── res
├── values-pt-rBR
└── strings.xml
├── values-zh-rTW
└── strings.xml
├── values-zh
└── strings.xml
└── values
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local directories
2 | /release
3 | /captures
4 | /proguard
5 | /design
6 | /dist
7 |
8 | # Local configuration file (sdk path, etc)
9 | /local.properties
10 | /keystore.jks
11 |
12 | # Android Studio
13 | .idea
14 | *.iml
15 | .gradle
16 | local.gradle
17 | build
18 |
19 | # OSX files
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Island
2 |
3 | Island for Android
4 |
5 | ## Build Instructions
6 |
7 | Island depends on ["deagle" library](https://github.com/oasisfeng/deagle), which must be cloned alongside Island in the same path.
8 |
9 | ```
10 | \--
11 | \- island
12 | \- deagle
13 | ```
14 |
15 | This project is constructed into several modules, with **assembly** module as the build portal,
16 | to support separate "light" build for core modules, in the form of "product flavor" in Gradle build configuration.
17 |
18 | The **"engine"** module shares the same package name with the **"complete"** build, to inherit the profile/device owner privilege.
19 | The **"mobile"** and other modules can be installed and updated separately alongside **"engine"** module for development convenience.
20 |
21 | ## Open API
22 |
23 | Due to the exclusivity nature, user could only use one Android DPC app at a time, and price of switching DPC is far too heavy. To encourage active exploration and broader development in the capabilities of DPC and therefore better benefit users,
24 | Island is devoted to build an open collaboration for community developers, either in development of this project or opening DPC capabilities to 3rd-party apps via open API. Island itself will not focus on the rich set of features, but mainly focuses on building a powerful **engine** as an open platform for much more apps from the community.
25 |
26 | Starting from the first public version of Island, all APIs are open to 3rd-party apps with the standard runtime-permission of Android as user authorization. Developers can start building apps now to take advantage of the Island open APIs.
27 |
28 | The protocol of all APIs are well defined and maintained in the **[class "Api"](/shared/src/main/java/com/oasisfeng/island/api/Api.java)**.
29 |
30 | ## Contribution
31 |
32 | If you found bugs, made minor improvements or translated the strings, please feel free to send us pull-requests.
33 |
34 | If you are interested in improving the functionality of Island, please create an issue first to discuss your thoughts with us, we are open to collaboration in future development.
35 |
36 | If you need new APIs for your apps to take advantage of the DPC capabilities, please feel free to create an issue to describe your app and its use case of those APIs. We are still in the early stage of building a rich set of open APIs.
37 |
--------------------------------------------------------------------------------
/assembly/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'com.google.gms.google-services'
3 | apply plugin: 'com.google.firebase.crashlytics'
4 |
5 | android {
6 | compileSdkVersion this.compileSdkVersion
7 |
8 | defaultConfig {
9 | applicationId "com.oasisfeng.island"
10 | minSdkVersion this.minSdkVersion
11 | targetSdkVersion 29 // Private APIs are accessed by module "open" and "fileprovider" (via indirectly loaded remote class)
12 | resConfigs "en", "zh", "zh-rTW"
13 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
14 | }
15 |
16 | buildFeatures.dataBinding true
17 |
18 | buildTypes {
19 | debug {
20 | firebaseCrashlytics.mappingFileUploadEnabled false
21 | }
22 | release {
23 | minifyEnabled true
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | flavorDimensions "packaging"
29 |
30 | productFlavors {
31 | complete {
32 | dimension "packaging"
33 | }
34 | engine { // Use the same application ID to retain the profile / device ownership between "engine" and "full" build.
35 | dimension "packaging"
36 | matchingFallbacks = ['full']
37 | }
38 | mobile {
39 | applicationIdSuffix ".mobile"
40 | dimension "packaging"
41 | matchingFallbacks = ['full']
42 | }
43 | fileprovider {
44 | applicationIdSuffix ".fileprovider"
45 | dimension "packaging"
46 | matchingFallbacks = ['full']
47 | }
48 | }
49 |
50 | compileOptions {
51 | sourceCompatibility JavaVersion.VERSION_1_8
52 | targetCompatibility JavaVersion.VERSION_1_8
53 | }
54 |
55 | lintOptions {
56 | check 'NewApi'
57 | abortOnError true
58 | htmlReport false
59 | xmlReport false
60 | textReport true
61 | textOutput "stdout"
62 | }
63 |
64 | repositories.flatDir {
65 | dirs '../app/libs'
66 | }
67 | }
68 |
69 | dependencies {
70 | // Complete
71 | completeImplementation project(':engine')
72 | completeImplementation project(':mobile')
73 | completeImplementation project(':fileprovider')
74 | completeImplementation project(':installer')
75 | completeImplementation project(':watcher')
76 | completeImplementation project(':open')
77 | // Engine only
78 | engineImplementation project(':engine')
79 | // Mobile only
80 | mobileImplementation project(':mobile')
81 | // File Provider only
82 | fileproviderImplementation project(':fileprovider')
83 | }
84 |
--------------------------------------------------------------------------------
/assembly/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\Android\SDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Shrink only
20 | -dontobfuscate
21 |
22 | # For AOSP internal disclosure in module "fileprovider"
23 | -keepclassmembers class * extends android.content.ContentResolver { *; }
24 | -dontwarn android.content.ContentResolver
25 | -dontwarn android.content.IContentProvider
26 |
27 | # Remove verbose and debug logging
28 | -assumenosideeffects class android.util.Log {
29 | public static boolean isLoggable(java.lang.String, int);
30 | public static int v(...);
31 | }
32 |
33 | # For generics reflection to work
34 | -keepattributes Signature
35 | -keepattributes *Annotation*
36 |
37 | # More debugging info (line number)
38 | -renamesourcefileattribute SourceFile
39 | -keepattributes SourceFile,LineNumberTable
40 |
--------------------------------------------------------------------------------
/assembly/src/complete/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:sharedUserId="com.oasisfeng.island" >
2 |
3 | <application android:label="@string/app_name" tools:replace="label" android:icon="@mipmap/ic_launcher"
4 | android:allowBackup="true" android:fullBackupContent="@xml/backup" tools:ignore="GoogleAppIndexingWarning" />
5 |
6 | </manifest>
7 |
--------------------------------------------------------------------------------
/assembly/src/complete/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="app_name">炼妖壶</string>
4 | </resources>
--------------------------------------------------------------------------------
/assembly/src/complete/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="app_name">Island</string>
4 | </resources>
--------------------------------------------------------------------------------
/assembly/src/complete/res/xml/backup.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <full-backup-content>
3 | <exclude domain="sharedpref" path="app_label_cache.xml" />
4 | </full-backup-content>
5 |
--------------------------------------------------------------------------------
/assembly/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:sharedUserId="com.oasisfeng.island"
2 | package="com.oasisfeng.island" android:versionCode="53000" android:versionName="5.3">
3 |
4 | <application tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
5 |
6 | <!-- Ensure Run/Debug Configuration in Android Studio to work properly for module-assembly build -->
7 | <activity android:name="com.oasisfeng.island.MainActivity">
8 | <intent-filter>
9 | <action android:name="android.intent.action.MAIN" />
10 | <category android:name="android.intent.category.LAUNCHER" />
11 | </intent-filter>
12 | </activity>
13 |
14 | </application>
15 |
16 | </manifest>
17 |
--------------------------------------------------------------------------------
/assembly/src/main/res/README:
--------------------------------------------------------------------------------
1 | Just a placeholder to keep this folder in repo for Crashlytics to build correctly.
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.4.32'
5 | ext.kotlin_coroutine_version = '1.3.9'
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath 'com.android.tools.build:gradle:4.1.3'
13 | classpath 'com.google.gms:google-services:4.3.4'
14 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
15 |
16 | // NOTE: Do not place your application dependencies here; they belong
17 | // in the individual module build.gradle files
18 | }
19 | }
20 |
21 | if (file('local.gradle').exists()) apply from: 'local.gradle'
22 |
23 | allprojects {
24 | repositories {
25 | google()
26 | jcenter()
27 | }
28 |
29 | ext.compileSdkVersion = 30
30 | ext.minSdkVersion = 24
31 | }
32 |
--------------------------------------------------------------------------------
/engine/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion this.compileSdkVersion
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 |
10 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
11 | }
12 |
13 | kotlinOptions.jvmTarget = "1.8"
14 |
15 | compileOptions {
16 | sourceCompatibility JavaVersion.VERSION_1_8
17 | targetCompatibility JavaVersion.VERSION_1_8
18 | }
19 |
20 | lintOptions {
21 | check 'NewApi'
22 | abortOnError false
23 | xmlReport false
24 | textReport true
25 | textOutput "stdout"
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation project(':shared')
31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
32 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutine_version"
33 | implementation 'androidx.core:core-ktx:1.3.2' // ShortcutShuttle
34 |
35 | androidTestImplementation 'androidx.test:runner:1.3.0'
36 | androidTestImplementation 'androidx.test:rules:1.3.0'
37 | }
--------------------------------------------------------------------------------
/engine/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.oasisfeng.island.engine.test">
2 | <uses-permission android:name="com.oasisfeng.island.permission.FREEZE_PACKAGE" />
3 | <uses-permission android:name="com.oasisfeng.island.permission.LAUNCH_PACKAGE" />
4 | <application>
5 | <activity android:name="com.oasisfeng.island.api.ApiActivityTest$DummyActivity">
6 | <intent-filter>
7 | <action android:name="android.intent.action.MAIN" />
8 | <category android:name="android.intent.category.LAUNCHER" />
9 | </intent-filter>
10 | </activity>
11 | <provider android:authorities="${applicationId}.AIP" android:name="com.oasisfeng.island.provisioning.AutoIncrementalProvision" android:enabled="false"/>
12 | </application>
13 | </manifest>
14 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/AppUpdateReceiver.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import com.oasisfeng.island.provisioning.IslandProvisioning;
8 | import com.oasisfeng.island.util.Users;
9 |
10 | /**
11 | * Handle {@link Intent#ACTION_MY_PACKAGE_REPLACED}
12 | *
13 | * Created by Oasis on 2017/7/20.
14 | */
15 | public class AppUpdateReceiver extends BroadcastReceiver {
16 |
17 | @Override public void onReceive(final Context context, final Intent intent) {
18 | // Currently, just blindly start the device owner provisioning, since it is idempotent, at least at present.
19 | if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()))
20 | if (Users.isOwner()) IslandProvisioning.startOwnerUserPostProvisioningIfNeeded(context);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/InternalActivity.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import com.oasisfeng.island.shuttle.ServiceShuttleActivity;
4 |
5 | /**
6 | * Placeholder for activity declaration, to keep stable exported entrances (especially important for shortcut) and allow internal refactoring.
7 | *
8 | * Created by Oasis on 2017/9/21.
9 | */
10 | public class InternalActivity {
11 |
12 | public static final class _1 extends ServiceShuttleActivity {}
13 | }
14 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/IslandDeviceAdminReceiver.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.app.admin.DeviceAdminReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import com.oasisfeng.island.provisioning.IslandProvisioning;
8 | import com.oasisfeng.island.util.ProfileUser;
9 |
10 | import static android.os.Build.VERSION.SDK_INT;
11 | import static android.os.Build.VERSION_CODES.O;
12 |
13 | /**
14 | * Handles events related to managed profile.
15 | */
16 | public class IslandDeviceAdminReceiver extends DeviceAdminReceiver {
17 |
18 | @ProfileUser @Override public void onProfileProvisioningComplete(final Context context, final Intent intent) {
19 | // DevicePolicyManager.ACTION_PROVISIONING_SUCCESSFUL is used instead of this trigger on Android O+.
20 | if (SDK_INT < O) IslandProvisioning.onProfileProvisioningComplete(context, intent);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/api/ApiReceiver.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.api;
2 |
3 | import android.app.Activity;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.util.Log;
8 |
9 | import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
10 |
11 | /**
12 | * Broadcast API invocation handler. Result will be delivered as broadcast result in ordered broadcast.
13 | *
14 | * Created by Oasis on 2017/9/18.
15 | */
16 | public class ApiReceiver extends BroadcastReceiver {
17 |
18 | @Override public void onReceive(final Context context, final Intent intent) {
19 | Log.i(TAG, "API request: " + intent.toUri(0));
20 | if ((intent.getFlags() & FLAG_RECEIVER_FOREGROUND) == 0) Log.w(TAG, "Add Intent.FLAG_RECEIVER_FOREGROUND to API intent for less delay.");
21 |
22 | String result = ApiDispatcher.verifyCaller(context, intent, null, -1);
23 | if (result != null) {
24 | setResultIfOrdered(Api.latest.RESULT_UNVERIFIED_IDENTITY, result);
25 | Log.w(TAG, "Caller verification failure: " + result);
26 | return;
27 | }
28 | result = ApiDispatcher.dispatch(context, intent);
29 | if (result == null) {
30 | setResultIfOrdered(Activity.RESULT_OK, null);
31 | Log.i(TAG, "API result: Success");
32 | } else {
33 | setResultIfOrdered(Activity.RESULT_CANCELED, result);
34 | Log.i(TAG, "API result: " + result);
35 | }
36 | }
37 |
38 | private void setResultIfOrdered(final int result, final String message) {
39 | if (isOrderedBroadcast()) setResult(result, message, null);
40 | }
41 |
42 | private static final String TAG = "API.Receiver";
43 | }
44 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/provisioning/AutoIncrementalProvision.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.provisioning;
2 |
3 | import com.oasisfeng.android.os.Loopers;
4 | import com.oasisfeng.island.engine.BuildConfig;
5 | import com.oasisfeng.island.util.ProfileUser;
6 | import com.oasisfeng.island.util.Users;
7 | import com.oasisfeng.pattern.PseudoContentProvider;
8 | import com.oasisfeng.perf.Performances;
9 | import com.oasisfeng.perf.Stopwatch;
10 |
11 | /**
12 | * Perform incremental provision
13 | *
14 | * Created by Oasis on 2017/11/21.
15 | */
16 | public class AutoIncrementalProvision extends PseudoContentProvider {
17 |
18 | @Override public boolean onCreate() {
19 | final Stopwatch stopwatch = Performances.startUptimeStopwatch();
20 | if (Users.isOwner()) {
21 | Loopers.addIdleTask(() -> IslandProvisioning.startOwnerUserPostProvisioningIfNeeded(context()));
22 | } else if (Users.isProfileManagedByIsland()) { // False if profile is not enabled yet. (during the broadcast ACTION_PROFILE_PROVISIONING_COMPLETE)
23 | final Thread thread = new Thread(this::startInProfile);
24 | thread.setPriority(Thread.MIN_PRIORITY);
25 | thread.start();
26 | }
27 | if (BuildConfig.DEBUG) Performances.check(stopwatch, 5, "IncPro.MainThread");
28 | return false;
29 | }
30 |
31 | @ProfileUser private void startInProfile() {
32 | final Stopwatch stopwatch = Performances.startUptimeStopwatch();
33 | IslandProvisioning.performIncrementalProfileOwnerProvisioningIfNeeded(context());
34 | if (BuildConfig.DEBUG) Performances.check(stopwatch, 10, "IncPro.WorkerThread");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/provisioning/ManualProvisioningReceiver.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.provisioning;
2 |
3 | import android.app.admin.DevicePolicyManager;
4 | import android.content.BroadcastReceiver;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.util.Log;
9 |
10 | import com.oasisfeng.island.util.DevicePolicies;
11 | import com.oasisfeng.island.util.Users;
12 |
13 | import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
14 | import static android.content.pm.PackageManager.DONT_KILL_APP;
15 |
16 | /**
17 | * Receiver for starting post-provisioning procedure for manual provisioning.
18 | *
19 | * The enabled state of this receiver in managed profile also serves as an indication of pending manual provisioning.
20 | *
21 | * Created by Oasis on 2017/4/8.
22 | */
23 | public class ManualProvisioningReceiver extends BroadcastReceiver { // Full class name must start with "com.oasisfeng.island.provision", see MainActivity.onCreateInProfile()
24 |
25 | @Override public void onReceive(final Context context, final Intent intent) {
26 | final String action = intent.getAction();
27 | if (action == null || Intent.ACTION_USER_INITIALIZE.equals(action)) {
28 | if (Users.isOwner()) return; // Should never happen
29 | if (Users.profile == null) {
30 | Log.d(TAG, "Profile is disabled"); // Profile is not enabled yet, that means we are currently in the managed provisioning flow
31 | return; // Nothing needs to be done here, we will receive ACTION_PROFILE_PROVISIONING_COMPLETE soon.
32 | }
33 | if (! Users.isProfileManagedByIsland()) {
34 | Log.d(TAG, "Not profile owner");
35 | return;
36 | }
37 | Log.i(TAG, (action != null ? "User initialized: " : "Provisioning resumed: ") + Users.toId(android.os.Process.myUserHandle()));
38 | IslandProvisioning.start(context, action);
39 |
40 | context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, getClass()), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP); // Disable self
41 | } else if (DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED.equals(action)){
42 | Log.i(TAG, "Device owner changed.");
43 | if (new DevicePolicies(context).isActiveDeviceOwner()) IslandProvisioning.start(context, action);
44 | }
45 | }
46 |
47 | private static final String TAG = ManualProvisioningReceiver.class.getSimpleName();
48 | }
49 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/provisioning/ProfileOwnerManualProvisioning.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.provisioning;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask;
7 | import com.oasisfeng.island.util.DevicePolicies;
8 | import com.oasisfeng.island.util.ProfileUser;
9 | import com.oasisfeng.island.util.Users;
10 |
11 | import static com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask.PROFILE_OWNER;
12 |
13 | /**
14 | * Simulate the managed provisioning procedure for manually enabled managed profile.
15 | *
16 | * Created by Oasis on 2016/4/18.
17 | */
18 | class ProfileOwnerManualProvisioning {
19 |
20 | @ProfileUser static void start(final Context context, final DevicePolicies policies) {
21 | new DeleteNonRequiredAppsTask(context, context.getPackageName(), PROFILE_OWNER, true, Users.toId(Users.current()), false, new DeleteNonRequiredAppsTask.Callback() {
22 | @Override public void onSuccess() {}
23 | @Override public void onError() { Log.e(TAG, "Error provisioning (task 1)"); }
24 | }).run();
25 |
26 | /* DisableBluetoothSharingTask & DisableInstallShortcutListenersTask cannot be done here, since they disable components. */
27 | /* Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH can be toggled in system Settings - Users & Profiles - Profile Settings */
28 | /* DISALLOW_WALLPAPER cannot be changed by profile / device owner. */
29 |
30 | // Set default cross-profile intent-filters
31 | CrossProfileIntentFiltersHelper.setFilters(policies);
32 | }
33 |
34 | private static final String TAG = ProfileOwnerManualProvisioning.class.getSimpleName();
35 | }
36 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/provisioning/task/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.oasisfeng.island.provisioning.task;
18 |
19 | import android.content.pm.ApplicationInfo;
20 | import android.content.pm.PackageManager;
21 | import android.os.RemoteException;
22 |
23 | import com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask.IPackageManager;
24 |
25 | import java.util.HashSet;
26 | import java.util.List;
27 | import java.util.Set;
28 |
29 | /**
30 | * Class containing various auxiliary methods.
31 | */
32 | class Utils {
33 |
34 | public Utils() {}
35 |
36 | /**
37 | * Returns the currently installed system apps on a given user.
38 | *
39 | * <p>Calls into the {@link IPackageManager} to retrieve all installed packages on the given
40 | * user and returns the package names of all system apps.
41 | *
42 | * @param ipm an {@link IPackageManager} object
43 | * @param userId the id of the user we are interested in
44 | */
45 | public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
46 | Set<String> apps = new HashSet<String>();
47 | List<ApplicationInfo> aInfos = null;
48 | try {
49 | aInfos = ipm.getInstalledApplications(
50 | PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList();
51 | } catch (RemoteException neverThrown) {
52 | ProvisionLogger.loge("This should not happen.", neverThrown);
53 | }
54 | for (ApplicationInfo aInfo : aInfos) {
55 | if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
56 | apps.add(aInfo.packageName);
57 | }
58 | }
59 | return apps;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/engine/src/main/java/com/oasisfeng/island/util/Dump.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | import android.os.Debug
4 | import android.os.ParcelFileDescriptor
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import kotlinx.coroutines.withTimeout
8 |
9 | /**
10 | * Helper for dumping system service
11 | *
12 | * Created by Oasis on 2019-6-24.
13 | */
14 | object Dump {
15 |
16 | suspend fun <T> systemService(service: String, vararg args: String, timeout: Long = 5_000, processor: (Sequence<String>) -> T): T? {
17 | val pipe = ParcelFileDescriptor.createPipe()
18 | pipe[1].use {
19 | if (! Debug.dumpService(service, it.fileDescriptor, if (args.isEmpty()) null else args)) return null
20 | return withContext(Dispatchers.IO) { // IO thread to receive dump to avoid infinite blocking in large dump.
21 | withTimeout(timeout) {
22 | ParcelFileDescriptor.AutoCloseInputStream(pipe[0]).bufferedReader().useLines(processor)
23 | }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/engine/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="permission_group_control_island_apps_label">Controle apps do Island</string>
4 | <string name="permission_group_control_island_apps_description">Controle apps gerenciados pelo Island</string>
5 | <string name="permission_freeze_app_label">Suspenda apps gerenciados pelo Island</string>
6 | <string name="permission_freeze_app_description">Este App pode suspender ou ativar qualquer app gerenciado pelo Island a qualquer hora.</string>
7 | <string name="permission_launch_app_label">Execute apps gerenciados pelo Island</string>
8 | <string name="permission_launch_app_description">Este App pode executar qualquer app gerenciado pelo Island, que será ativado se for necessário.</string>
9 | <string name="permission_suspend_app_label">Suspenda apps gerenciados pelo Island</string>
10 | <string name="permission_suspend_app_description">Este App pode suspender ou ativar qualquer app gerenciado pelo Island a qualquer hora.</string>
11 |
12 | <string name="notification_provisioning_island_title">Disponibilizando Island…</string>
13 | <string name="notification_provisioning_mainland_title">Disponibilizando Mainland…</string>
14 | <string name="notification_provisioning_text">Pode levar alguns minutos.</string>
15 |
16 | <string name="toast_shortcut_invalid">O atalho não é mais válido, favor verificar o status ou recriá-lo no Island</string>
17 | <string name="toast_setup_complete">A configuração do Island está concluída.</string>
18 | <string name="toast_reprovision_done">Conserto feito.</string>
19 | </resources>
20 |
--------------------------------------------------------------------------------
/engine/src/main/res/values-v24/disallowed_apps_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed profile. -->
21 | <string-array name="disallowed_apps_managed_profile">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="permission_group_control_island_apps_label">煉妖壺管理的應用程式</string>
4 | <string name="permission_group_control_island_apps_description">控制煉妖壺管理的應用程式</string>
5 | <string name="permission_freeze_app_label">凍結煉妖壺管理的應用程式</string>
6 | <string name="permission_freeze_app_description">這個應用程式被授權可隨時凍結及解凍煉妖壺管理的任意應用程式</string>
7 | <string name="permission_launch_app_label">啟動煉妖壺管理的應用程式</string>
8 | <string name="permission_launch_app_description">這個應用程式被授權可啟動煉妖壺管理的任何應用程式,凍結的應用程式将在啟動前解凍。</string>
9 | <string name="permission_suspend_app_label">暫停煉妖壺管理的應用程式</string>
10 | <string name="permission_suspend_app_description">這個應用程式被授權可隨時暫停及恢復煉妖壺管理的任意應用程式</string>
11 |
12 | <string name="notification_provisioning_island_title">正在準備 壺中界…</string>
13 | <string name="notification_provisioning_mainland_title">正在接管 界外…</string>
14 | <string name="notification_provisioning_text">可能需要數分鐘之久</string>
15 |
16 | <string name="toast_shortcut_invalid">快速啟動方式已失效,請檢查壺中界的狀態或重新建立此快速啟動方式。</string>
17 | <string name="toast_setup_complete">壺中界已建立完成。</string>
18 | <string name="toast_reprovision_done">已完成回爐修整</string>
19 | </resources>
--------------------------------------------------------------------------------
/engine/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="permission_group_control_island_apps_label">炼妖壶管理的应用</string>
4 | <string name="permission_group_control_island_apps_description">控制炼妖壶管理的应用</string>
5 | <string name="permission_freeze_app_label">冻结炼妖壶管理的应用</string>
6 | <string name="permission_freeze_app_description">这个应用被授权可随时冻结及解冻炼妖壶管理的任意应用</string>
7 | <string name="permission_launch_app_label">启动炼妖壶管理的应用</string>
8 | <string name="permission_launch_app_description">这个应用被授权可启动炼妖壶管理的任何应用,冻结的应用将在启动前解冻。</string>
9 | <string name="permission_suspend_app_label">暂停炼妖壶管理的应用</string>
10 | <string name="permission_suspend_app_description">这个应用被授权可随时暂停及恢复炼妖壶管理的任意应用</string>
11 |
12 | <string name="notification_provisioning_island_title">正在准备 壶中界…</string>
13 | <string name="notification_provisioning_mainland_title">正在接管 界外…</string>
14 | <string name="notification_provisioning_text">可能需要数分钟之久</string>
15 |
16 | <string name="toast_shortcut_invalid">快捷方式已失效,请检查壶中界的状态或重新创建此快捷方式。</string>
17 | <string name="toast_setup_complete">壶中界已完成创建。</string>
18 | <string name="toast_reprovision_done">已完成回炉修整</string>
19 | </resources>
--------------------------------------------------------------------------------
/engine/src/main/res/values/disallowed_apps_managed_device.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed device. -->
21 | <string-array name="disallowed_apps_managed_device">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/disallowed_apps_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed profile. -->
21 | <string-array name="disallowed_apps_managed_profile">
22 | <item>com.android.server.telecom</item>
23 | </string-array>
24 | </resources>
25 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/disallowed_apps_managed_user.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed user. -->
21 | <string-array name="disallowed_apps_managed_user">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/packages_to_delete_new_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of packages to be deleted on new managed profile. -->
21 | <string-array name="packages_to_delete_new_managed_profile">
22 | <item>com.android.server.telecom</item>
23 | </string-array>
24 | </resources>
25 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/required_apps_managed_device.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained on the managed device.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="required_apps_managed_device">
23 | <item>com.android.settings</item>
24 | <item>com.android.contacts</item>
25 | <item>com.android.dialer</item>
26 | <item>com.android.stk</item> <!-- Required by com.android.phone by certain carriers -->
27 | <item>com.android.providers.downloads</item>
28 | <item>com.android.providers.downloads.ui</item>
29 | <item>com.android.documentsui</item>
30 | </string-array>
31 | </resources>
32 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/required_apps_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained in the managed profile.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="required_apps_managed_profile">
23 | <item>com.android.contacts</item>
24 | <item>com.android.settings</item>
25 | <item>com.android.providers.downloads</item>
26 | <item>com.android.providers.downloads.ui</item>
27 | <item>com.android.documentsui</item>
28 | </string-array>
29 | </resources>
30 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/required_apps_managed_user.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained on the managed user.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="required_apps_managed_user">
23 | <item>com.android.settings</item>
24 | <item>com.android.contacts</item>
25 | <item>com.android.dialer</item>
26 | <item>com.android.stk</item> <!-- Required by com.android.phone by certain carriers -->
27 | <item>com.android.providers.downloads</item>
28 | <item>com.android.providers.downloads.ui</item>
29 | <item>com.android.documentsui</item>
30 | </string-array>
31 | </resources>
32 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="permission_group_control_island_apps_label">Control Island apps</string>
4 | <string name="permission_group_control_island_apps_description">control apps managed by Island</string>
5 | <string name="permission_freeze_app_label">Freeze Island-managed apps</string>
6 | <string name="permission_freeze_app_description">This app can freeze and unfreeze any app managed by Island at any time.</string>
7 | <string name="permission_launch_app_label">Launch Island-managed apps</string>
8 | <string name="permission_launch_app_description">This app can launch any app managed by Island, which will be unfrozen if needed.</string>
9 | <string name="permission_suspend_app_label">Suspend Island-managed apps</string>
10 | <string name="permission_suspend_app_description">This app can suspend and unsuspend any app managed by Island at any time.</string>
11 |
12 | <string name="notification_provisioning_island_title">Provisioning Island…</string>
13 | <string name="notification_provisioning_mainland_title">Provisioning Mainland…</string>
14 | <string name="notification_provisioning_text">It may take several minutes.</string>
15 |
16 | <string name="toast_shortcut_invalid">Shortcut is no longer valid, please check the status of Island or re-create it in Island</string>
17 | <string name="toast_setup_complete">Island setup is complete.</string>
18 | <string name="toast_reprovision_done">Done repairing.</string>
19 | </resources>
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_disallowed_apps_managed_device.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed device by a particular vendor. -->
21 | <string-array name="vendor_disallowed_apps_managed_device">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_disallowed_apps_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed profile by a particular vendor. -->
21 | <string-array name="vendor_disallowed_apps_managed_profile">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_disallowed_apps_managed_user.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2015 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be removed from the managed user by a particular vendor. -->
21 | <string-array name="vendor_disallowed_apps_managed_user">
22 | </string-array>
23 | </resources>
24 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_required_apps_managed_device.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained on the managed device by a particular vendor.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="vendor_required_apps_managed_device">
23 | </string-array>
24 | </resources>
25 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_required_apps_managed_profile.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained in the managed profile by a particular vendor.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="vendor_required_apps_managed_profile">
23 | </string-array>
24 | </resources>
25 |
--------------------------------------------------------------------------------
/engine/src/main/res/values/vendor_required_apps_managed_user.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | /**
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | -->
19 | <resources>
20 | <!-- A list of apps to be retained on the managed user by a particular vendor.
21 | Takes precedence over the disallowed apps lists. -->
22 | <string-array name="vendor_required_apps_managed_user">
23 | </string-array>
24 | </resources>
25 |
--------------------------------------------------------------------------------
/engine/src/main/res/xml/device_admin.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <device-admin>
3 | <uses-policies>
4 | <force-lock/>
5 | <wipe-data/>
6 | <disable-camera/>
7 | <disable-keyguard-features/>
8 | <!--<encrypted-storage/>-->
9 | <!--<watch-login/>-->
10 | <!--<limit-password/>-->
11 | <!--<expire-password/>-->
12 | <!--<reset-password/>-->
13 | <!--<set-global-proxy/>-->
14 | </uses-policies>
15 | </device-admin>
16 |
--------------------------------------------------------------------------------
/fileprovider/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion this.compileSdkVersion
5 |
6 | defaultConfig {
7 | minSdkVersion this.minSdkVersion
8 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
9 | }
10 |
11 | compileOptions {
12 | sourceCompatibility JavaVersion.VERSION_1_8
13 | targetCompatibility JavaVersion.VERSION_1_8
14 | }
15 | }
16 |
17 | android.libraryVariants.all { variant ->
18 | String taskName = String.format("remove%sStubClasses", variant.name.capitalize())
19 | task "${taskName}" {
20 | doLast { delete "${buildDir}/intermediates/classes/${variant.name}" + "/android" }
21 | }
22 | variant.processJavaResources.dependsOn(taskName)
23 | }
24 |
25 | dependencies {
26 | implementation project(':shared')
27 | implementation 'androidx.annotation:annotation:1.1.0'
28 |
29 | testImplementation 'junit:junit:4.12'
30 | androidTestImplementation 'androidx.test:runner:1.3.0'
31 | }
32 |
--------------------------------------------------------------------------------
/fileprovider/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest package="com.oasisfeng.island.fileprovider" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
2 |
3 | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
4 | <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" tools:ignore="ProtectedPermissions" /> <!-- EventReceiver -->
5 |
6 | <application android:label="Island - File Shuttle" tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
7 |
8 | <!-- ShuttleProvider to proxy the content requests across profile. -->
9 | <provider android:authorities="com.oasisfeng.island.files.shuttle"
10 | android:name=".ShuttleProvider"
11 | android:permission="android.permission.MANAGE_DOCUMENTS"
12 | android:grantUriPermissions="true"
13 | android:exported="true"
14 | android:enabled="false"
15 | android:process=":file_shuttle"> <!-- use separate process to avoid unnecessary initialization of this provider -->
16 | <intent-filter>
17 | <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
18 | </intent-filter>
19 | </provider>
20 |
21 | <provider android:authorities="com.oasisfeng.island.files"
22 | android:name=".ExternalStorageProviderProxy"
23 | android:exported="false"
24 | android:process=":file"/> <!-- Always running in different user from ShuttleProvider, therefore never use the same process. -->
25 |
26 | </application>
27 | </manifest>
28 |
--------------------------------------------------------------------------------
/fileprovider/src/main/java/android/content/IContentProvider.java:
--------------------------------------------------------------------------------
1 | package android.content;
2 |
3 | import android.os.IInterface;
4 | import androidx.annotation.Keep;
5 | import androidx.annotation.RestrictTo;
6 |
7 | /**
8 | * Stub class for real IContentProvider, only for compilation purpose.
9 | *
10 | * Created by Oasis on 2017/4/11.
11 | */
12 | @Keep @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
13 | public interface IContentProvider extends IInterface {
14 | }
15 |
--------------------------------------------------------------------------------
/fileprovider/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="file_shuttle_summary">Cruzamento pelo Island</string>
3 | </resources>
4 |
--------------------------------------------------------------------------------
/fileprovider/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="file_shuttle_summary">煉妖壺·彼界</string>
3 | </resources>
4 |
--------------------------------------------------------------------------------
/fileprovider/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="file_shuttle_summary">炼妖壶·彼界</string>
3 | </resources>
4 |
--------------------------------------------------------------------------------
/fileprovider/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="file_shuttle_summary">Shuttled by Island</string>
3 | </resources>
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.enableJetifier=true
21 | android.useAndroidX=true
22 |
23 | # Disable instrumentation by Firebase Performance
24 | firebasePerformanceInstrumentationEnabled=false
25 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 08 16:01:04 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/installer/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 |
5 | android {
6 | compileSdkVersion this.compileSdkVersion
7 |
8 | defaultConfig {
9 | minSdkVersion this.minSdkVersion
10 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
11 | }
12 |
13 | kotlinOptions.jvmTarget = "1.8"
14 |
15 | compileOptions {
16 | sourceCompatibility JavaVersion.VERSION_1_8
17 | targetCompatibility JavaVersion.VERSION_1_8
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation project(':shared')
23 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
24 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
25 | implementation "com.jaredrummler:apk-parser:1.0.2"
26 |
27 | testImplementation 'junit:junit:4.12'
28 | androidTestImplementation 'androidx.test:runner:1.3.0'
29 | }
30 |
--------------------------------------------------------------------------------
/installer/src/main/res/layout/dialog_checkbox.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
3 | android:layout_width="fill_parent"
4 | android:layout_height="wrap_content">
5 |
6 | <CheckBox android:id="@+id/checkbox"
7 | android:layout_width="fill_parent"
8 | android:layout_height="wrap_content"
9 | android:layout_marginTop="18dp"
10 | android:layout_marginStart="20dp"
11 | android:layout_marginEnd="20dp"
12 | tools:text="@string/dialog_install_checkbox_always_allow"
13 | style="@android:style/TextAppearance.Material.Widget" /> <!-- android:paddingStart="?attr/dialogPreferredPadding" -->
14 |
15 | </FrameLayout>
16 |
--------------------------------------------------------------------------------
/installer/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="app_installer_label">應用程式安裝器</string>
4 |
5 | <string name="confirm_installing">“%1$s”請求安裝 %2$s。\n\n是否繼續?</string>
6 | <string name="confirm_updating">“%1$s”請求更新 %2$s。\n\n是否繼續?</string>
7 | <string name="confirm_cloning">“%1$s”請求複製 %2$s。\n\n是否繼續?</string>
8 | <string name="description_for_installing_app">“%1$s” (%2$s) 版本 %3$s (%4$d)</string>
9 | <string name="description_for_cloning_app">“%1$s” (%2$s)</string>
10 | <string name="label_unknown_app">應用程式</string>
11 | <string name="dialog_install_checkbox_always_allow">始終允許此來源安裝應用程式</string>
12 | <string name="dialog_install_failure_title">安裝失敗:%s</string>
13 | <string name="dialog_install_unknown_failure_message">Android 系统的應用程式安裝過程發生了內部錯誤</string>
14 | <string name="progress_dialog_installing">“%1$s”正在安裝 %2$s</string>
15 | <string name="progress_dialog_updating">“%1$s”正在更新 %2$s</string>
16 | <string name="progress_dialog_cloning">“%1$s”正在複製 %2$s</string>
17 |
18 | <string name="notification_caller_installed_app">“%1$s”已安裝新應用程式“%2$s”</string>
19 | <string name="notification_caller_updated_app">“%1$s”已更新“%2$s”</string>
20 | <string name="notification_caller_updated_self">\"%1$s\"已更新自己</string>
21 | <string name="notification_app_with_permissions">已取得的權限:%s</string>
22 | <string name="notification_app_target_android_version">適配 Android 版本: %s</string>
23 | <string name="notification_app_target_android_version_update">適配 Android: %1$s -> %2$s</string>
24 | <string name="action_show_app_settings">應用程式設定</string>
25 |
26 | <string name="app_info_forwarder_title">在哪一個應用程式中顯示關於 %1$s (%2$s) 的訊息</string>
27 | <string name="app_settings_helper_prompt">點擊“強制停止”可打開其它工具中針對此應用程式的設定</string>
28 | <string name="fallback_to_sys_installer">嘗試傳統安裝方式</string>
29 | <string name="toast_split_apk_clone_fallback_warning">此應用程式含有附加組件,可能無法以這種方式正常複製。如果遇到故障,請嘗試重新從應用程式商店安裝。</string>
30 |
31 | <string name="prompt_miui_optimization">可能是“MIUI 優化”導致(可在“系统設定 - 開發人員選項”中關閉)</string>
32 | </resources>
--------------------------------------------------------------------------------
/installer/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
3 | <string name="app_installer_label">应用安装器</string>
4 |
5 | <string name="confirm_installing">“%1$s”请求安装 %2$s。\n\n是否继续?</string>
6 | <string name="confirm_updating">“%1$s”请求更新 %2$s。\n\n是否继续?</string>
7 | <string name="confirm_cloning">“%1$s”请求克隆 %2$s。\n\n是否继续?</string>
8 | <string name="description_for_installing_app">“%1$s” (%2$s) 版本 %3$s (%4$d)</string>
9 | <string name="description_for_cloning_app">“%1$s” (%2$s)</string>
10 | <string name="label_unknown_app">应用</string>
11 | <string name="dialog_install_checkbox_always_allow">始终允许此来源安装应用</string>
12 | <string name="dialog_install_failure_title">安装失败:%s</string>
13 | <string name="dialog_install_unknown_failure_message">Android 系统的应用安装过程发生了内部错误</string>
14 | <string name="prompt_install_aborted">应用安装中止</string>
15 | <string name="progress_dialog_installing">“%1$s”正在安装 %2$s</string>
16 | <string name="progress_dialog_updating">“%1$s”正在更新 %2$s</string>
17 | <string name="progress_dialog_expanding">“%1$s”正在扩展 %2$s</string>
18 | <string name="progress_dialog_cloning">“%1$s”正在克隆 %2$s</string>
19 |
20 | <string name="notification_caller_installed_app">“%1$s”已安装新应用 %2$s</string>
21 | <string name="notification_caller_updated_app">“%1$s”已更新 %2$s</string>
22 | <string name="notification_caller_updated_self">\"%1$s\"已更新自己</string>
23 | <string name="notification_caller_expanded_app">\"%1$s\"已扩展 %2$s</string>
24 | <string name="notification_caller_cloned_app">\"%1$s\"已克隆 %2$s</string>
25 | <string name="notification_app_with_permissions">已获取的权限:%s</string>
26 | <string name="notification_app_version">版本: %s</string>
27 | <string name="notification_app_version_update">版本: %1$s -> %2$s</string>
28 | <string name="notification_app_target_android_version">适配 Android 版本: %s</string>
29 | <string name="notification_app_target_android_version_update">适配 Android: %1$s -> %2$s</string>
30 | <string name="notification_app_target_android_10_legacy_storage">10 (非隔离存储)</string>
31 | <string name="action_show_app_settings">应用设置</string>
32 |
33 | <string name="app_info_forwarder_title">在哪一个应用中显示关于 %1$s (%2$s) 的信息</string>
34 | <string name="app_settings_helper_prompt">点击“强制停止”可打开其它工具中针对此应用的设置</string>
35 | <string name="fallback_to_sys_installer">尝试传统安装方式</string>
36 | <string name="toast_split_apk_clone_fallback_warning">此应用含有附加组件,可能无法以这种方式正常克隆。如果遇到故障,请尝试重新从应用市场安装。</string>
37 |
38 | <string name="prompt_miui_optimization">可能是“MIUI 优化”导致(可在“系统设置 - 开发者选项”中关闭)</string>
39 | </resources>
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/mobile/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion this.compileSdkVersion
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
10 | }
11 |
12 | buildFeatures.dataBinding true
13 |
14 | kotlinOptions.jvmTarget = "1.8"
15 |
16 | compileOptions {
17 | sourceCompatibility JavaVersion.VERSION_1_8
18 | targetCompatibility JavaVersion.VERSION_1_8
19 | }
20 |
21 | lintOptions {
22 | check 'NewApi'
23 | abortOnError true
24 | xmlReport false
25 | textReport true
26 | textOutput "stdout"
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.aar'])
32 | implementation project(':shared')
33 | implementation project(':deagle')
34 |
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
36 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
37 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutine_version"
38 | implementation 'androidx.core:core-ktx:1.3.2'
39 | implementation "androidx.fragment:fragment-ktx:1.2.5"
40 | implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'
41 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
42 | implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
43 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
44 | implementation "androidx.biometric:biometric:1.0.1"
45 | implementation 'com.google.android.material:material:1.2.1'
46 | implementation 'androidx.cardview:cardview:1.0.0'
47 | implementation 'com.google.firebase:firebase-appindexing:19.1.0' // FeatureActionActivity
48 | implementation 'eu.chainfire:libsuperuser:1.0.0.201510071325'
49 | implementation 'uk.co.samuelwall:material-tap-target-prompt:2.0.1'
50 |
51 | androidTestImplementation 'org.mockito:mockito-android:2.8.9'
52 | androidTestImplementation 'androidx.test:runner:1.3.0'
53 | }
54 |
--------------------------------------------------------------------------------
/mobile/libs/setup-wizard-lib-platform-release.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/libs/setup-wizard-lib-platform-release.aar
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/common/app/BaseAndroidViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.common.app
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.AndroidViewModel
5 |
6 | abstract class BaseAndroidViewModel(app: Application): AndroidViewModel(app) {
7 |
8 | abstract val tag: String
9 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/common/app/BaseAppViewModel.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.common.app;
2 |
3 | import android.graphics.drawable.Drawable;
4 | import android.text.TextUtils;
5 | import android.view.View;
6 |
7 | import androidx.databinding.ObservableField;
8 | import androidx.lifecycle.ViewModel;
9 |
10 | import com.oasisfeng.android.ui.IconResizer;
11 | import com.oasisfeng.androidx.lifecycle.NonNullMutableLiveData;
12 | import com.oasisfeng.island.IslandApplication;
13 | import com.oasisfeng.island.mobile.R;
14 |
15 | import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
16 |
17 | /**
18 | * View-model of basic app entry
19 | *
20 | * Created by Oasis on 2016/8/11.
21 | */
22 | public class BaseAppViewModel extends ViewModel {
23 |
24 | public final AppInfo info;
25 | public final ObservableField<Drawable> icon = new ObservableField<>(); // Issue in data-binding - MutableLiveData causes initially empty icons.
26 | public transient final NonNullMutableLiveData<Boolean> selected = new NonNullMutableLiveData<>(false);
27 | private volatile boolean mIconLoadingStarted;
28 |
29 | public boolean isSystem() { return (info.flags & FLAG_SYSTEM) != 0; }
30 |
31 | @SuppressWarnings("unused") // Used by data binding
32 | public void onViewAttached(final View v) {
33 | if (mIconLoadingStarted) return;
34 | mIconLoadingStarted = true;
35 | info.loadUnbadgedIcon(sIconResizer::createIconThumbnail, icon::set);
36 | }
37 |
38 | public BaseAppViewModel(final AppInfo info) { this.info = info; }
39 |
40 | /* Helper functions for implementing ObservableSortedList.Sortable */
41 |
42 | protected boolean isSameAs(final BaseAppViewModel another) {
43 | return this == another || info.packageName.equals(another.info.packageName);
44 | }
45 |
46 | protected boolean isContentSameAs(final BaseAppViewModel another) {
47 | return TextUtils.equals(info.getLabel(), another.info.getLabel()) && info.flags == another.info.flags;
48 | }
49 |
50 | private final static IconResizer sIconResizer = new IconResizer((int) IslandApplication.$().getResources().getDimension(R.dimen.app_icon_size)); // TODO: Avoid static
51 | }
52 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/TempDebug.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island
2 |
3 | import android.app.Activity
4 |
5 | /**
6 | * Stub for temporary debugging code, which is not supposed to be committed to code repository.
7 | *
8 | * Created by Oasis on 2016/8/20.
9 | */
10 | object TempDebug {
11 |
12 | @JvmStatic fun run(activity: Activity) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/adb/ProfileRestrictionsSync.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.adb;
2 |
3 | import android.app.admin.DevicePolicyManager;
4 | import android.content.BroadcastReceiver;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.os.Bundle;
10 | import android.os.UserManager;
11 | import android.util.Log;
12 |
13 | import com.oasisfeng.island.util.DevicePolicies;
14 | import com.oasisfeng.island.util.Users;
15 |
16 | import static android.os.UserManager.DISALLOW_DEBUGGING_FEATURES;
17 |
18 | /**
19 | * Sync certain user restrictions upon profile starting
20 | *
21 | * Created by Oasis on 2019-5-24.
22 | */
23 | public class ProfileRestrictionsSync extends BroadcastReceiver {
24 |
25 | @Override public void onReceive(final Context context, final Intent intent) {
26 | final String action = intent.getAction();
27 | if (! Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action) && ! Intent.ACTION_BOOT_COMPLETED.equals(action)) return;
28 | final DevicePolicies policies = new DevicePolicies(context);
29 | if (Users.isOwner() || ! policies.isProfileOwner()) { // This receiver is not needed in owner user or profile not managed by Island.
30 | context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, getClass()),
31 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
32 | return;
33 | }
34 | final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
35 | final Bundle owner_restrictions = um.getUserRestrictions(Users.owner);
36 | final Bundle profile_restrictions = policies.invoke(DevicePolicyManager::getUserRestrictions);
37 | final boolean enabled_in_owner = owner_restrictions.getBoolean(DISALLOW_DEBUGGING_FEATURES);
38 | if (profile_restrictions.getBoolean(DISALLOW_DEBUGGING_FEATURES) != enabled_in_owner) {
39 | policies.setUserRestriction(DISALLOW_DEBUGGING_FEATURES, enabled_in_owner);
40 | Log.i(TAG, "Synchronized: " + DISALLOW_DEBUGGING_FEATURES + " = " + enabled_in_owner);
41 | }
42 | }
43 |
44 | private static final String TAG = "Island.PRS";
45 | }
46 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/data/LiveProfileState.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.data
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.os.UserHandle
8 | import android.os.UserManager
9 | import android.util.ArrayMap
10 | import androidx.lifecycle.LiveData
11 | import androidx.lifecycle.Observer
12 |
13 | class LiveProfileStates(private val context: Context) {
14 |
15 | enum class ProfileState { UNAVAILABLE, AVAILABLE, UNLOCKED }
16 |
17 | inner class LiveProfileState(private val profile: UserHandle): LiveData<ProfileState>() {
18 |
19 | internal fun updateFromBroadcast(intent: Intent) {
20 | value = when (intent.action) {
21 | Intent.ACTION_MANAGED_PROFILE_AVAILABLE -> ProfileState.AVAILABLE
22 | Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> ProfileState.UNLOCKED
23 | Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> ProfileState.UNAVAILABLE
24 | else -> throw IllegalStateException("Unexpected broadcast: $intent") }
25 | }
26 |
27 | override fun onActive() {
28 | mLiveBroadcastReceiver.observeForever(mDummyObserver)
29 | value = context.getSystemService(UserManager::class.java)!!.run {
30 | try { when {
31 | isUserUnlocked(profile) -> ProfileState.UNLOCKED
32 | isUserRunning(profile) -> ProfileState.AVAILABLE
33 | else -> ProfileState.UNAVAILABLE
34 | }} catch (e: SecurityException) { ProfileState.UNAVAILABLE }} // "SecurityException: You need INTERACT_ACROSS_USERS or MANAGE_USERS permission to ...", probably due to Island being destroyed.
35 | }
36 |
37 | override fun onInactive() = mLiveBroadcastReceiver.removeObserver(mDummyObserver)
38 |
39 | private val mDummyObserver = Observer<Unit> {}
40 | }
41 |
42 | fun get(profile: UserHandle): LiveProfileState = states.getOrPut(profile) { LiveProfileState(profile) }
43 |
44 | val states = ArrayMap<UserHandle, LiveProfileState>()
45 |
46 | val mLiveBroadcastReceiver = object: LiveData<Unit>() {
47 |
48 | val ACTIONS = arrayOf(Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNLOCKED, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
49 |
50 | override fun onActive() { context.registerReceiver(mReceiver, IntentFilter().apply { ACTIONS.forEach { addAction(it) }}) }
51 | override fun onInactive() = context.unregisterReceiver(mReceiver)
52 |
53 | private val mReceiver = object: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) {
54 | states[intent.getParcelableExtra(Intent.EXTRA_USER) ?: return]?.updateFromBroadcast(intent) }}
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/data/helper/AppStateTrackingHelper.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.data.helper;
2 |
3 | import android.app.Activity;
4 | import android.app.Fragment;
5 | import android.os.Bundle;
6 | import android.os.UserHandle;
7 |
8 | import com.oasisfeng.island.data.IslandAppListProvider;
9 | import com.oasisfeng.island.util.Users;
10 |
11 | /**
12 | * Helper for tracking the state of apps.
13 | *
14 | * Created by Oasis on 2019-1-23.
15 | */
16 | public class AppStateTrackingHelper {
17 |
18 | /** Force refresh the state of specified app when activity is resumed */
19 | public static void requestSyncWhenResumed(final Activity activity, final String pkg, final UserHandle user) {
20 | activity.getFragmentManager().beginTransaction().add(AppStateSyncFragment.create(pkg, user), pkg + "@" + Users.toId(user)).commit();
21 | }
22 |
23 | @SuppressWarnings("deprecation") public static class AppStateSyncFragment extends Fragment {
24 |
25 | private static final String KEY_PACKAGE = "package", KEY_USER = "user";
26 |
27 | @Override public void onResume() {
28 | super.onResume();
29 | final Activity activity = getActivity(); final Bundle args = getArguments();
30 | final String pkg = args.getString(KEY_PACKAGE); final UserHandle user = args.getParcelable(KEY_USER);
31 | if (pkg != null && user != null)
32 | IslandAppListProvider.getInstance(activity).refreshPackage(pkg, user, false);
33 | activity.getFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss();
34 | }
35 |
36 | private static AppStateSyncFragment create(final String pkg, final UserHandle user) {
37 | final AppStateSyncFragment fragment = new AppStateSyncFragment();
38 | final Bundle args = new Bundle();
39 | args.putString(KEY_PACKAGE, pkg);
40 | args.putParcelable(KEY_USER, user);
41 | fragment.setArguments(args);
42 | return fragment;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/featured/FeaturedViewModel.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.featured;
2 |
3 | import android.app.Application;
4 | import android.graphics.drawable.Drawable;
5 |
6 | import com.oasisfeng.android.databinding.ObservableSortedList;
7 | import com.oasisfeng.androidx.lifecycle.NonNullMutableLiveData;
8 |
9 | import java.util.Objects;
10 | import java.util.function.Consumer;
11 |
12 | import androidx.annotation.NonNull;
13 | import androidx.annotation.Nullable;
14 | import androidx.lifecycle.AndroidViewModel;
15 | import androidx.lifecycle.LiveData;
16 |
17 | /**
18 | * Created by Oasis on 2018/5/18.
19 | */
20 | public class FeaturedViewModel extends AndroidViewModel implements ObservableSortedList.Sortable<FeaturedViewModel> {
21 |
22 | public final String tag;
23 | public final String title;
24 | public final CharSequence description;
25 | public final @Nullable Drawable icon;
26 | public final LiveData<Integer> button;
27 | public final @Nullable Consumer<FeaturedViewModel> function;
28 | public final NonNullMutableLiveData<Boolean> dismissed;
29 |
30 | @Override public boolean isSameAs(final FeaturedViewModel another) {
31 | return tag.equals(another.tag);
32 | }
33 |
34 | @Override public boolean isContentSameAs(final FeaturedViewModel o) {
35 | return Objects.equals(title, o.title) && Objects.equals(description, o.description) && Objects.equals(button, o.button);
36 | }
37 |
38 | @Override public int compareTo(@NonNull final FeaturedViewModel o) {
39 | int result = dismissed.getValue().compareTo(o.dismissed.getValue());
40 | if (result == 0) result = Integer.compare(order, o.order);
41 | return result;
42 | }
43 |
44 | FeaturedViewModel(final Application app, final int order, final String tag, final String title, final CharSequence description,
45 | final @Nullable Drawable icon, final LiveData</* @StringRes */Integer> button,
46 | final @Nullable Consumer<FeaturedViewModel> function, final boolean dismissed) {
47 | super(app);
48 | this.order = order;
49 | this.tag = tag;
50 | this.title = title;
51 | this.description = description;
52 | this.icon = icon;
53 | this.button = button;
54 | this.function = function;
55 | this.dismissed = new NonNullMutableLiveData<>(dismissed);
56 | }
57 |
58 | private final int order;
59 | }
60 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/model/IslandViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.model
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.widget.Toast
6 | import androidx.lifecycle.viewModelScope
7 | import com.oasisfeng.common.app.BaseAndroidViewModel
8 | import com.oasisfeng.island.analytics.Analytics
9 | import kotlinx.coroutines.CoroutineExceptionHandler
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.future.future
12 | import kotlinx.coroutines.launch
13 | import java.util.concurrent.CancellationException
14 | import java.util.concurrent.CompletableFuture
15 |
16 | fun BaseAndroidViewModel.interactive(context: Context, block: suspend CoroutineScope.() -> Unit) {
17 | viewModelScope.launch(CoroutineExceptionHandler { _, e -> handleException(context, tag, e) }, block = block)
18 | }
19 |
20 | fun <R> BaseAndroidViewModel.interactiveFuture(context: Context, block: suspend CoroutineScope.() -> R): CompletableFuture<R?> {
21 | return viewModelScope.future(block = block).exceptionally { t -> null.also { handleException(context, tag, t) }}
22 | }
23 |
24 | private fun handleException(context: Context, tag: String, t: Throwable) {
25 | if (t is CancellationException) return Unit.also { Log.i(tag, "Interaction canceled: ${t.message}") }
26 | Analytics().logAndReport(tag, "Unexpected internal error", t)
27 | Toast.makeText(context, "Internal error: " + t.message, Toast.LENGTH_LONG).show()
28 | }
29 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/model/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.model
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.os.Build.VERSION.SDK_INT
6 | import android.os.Build.VERSION_CODES.Q
7 | import android.os.UserHandle
8 | import android.util.Log
9 | import androidx.fragment.app.FragmentActivity
10 | import androidx.lifecycle.SavedStateHandle
11 | import com.google.android.material.tabs.TabLayout
12 | import com.oasisfeng.island.analytics.analytics
13 | import com.oasisfeng.island.data.LiveProfileStates
14 | import com.oasisfeng.island.data.LiveProfileStates.ProfileState
15 | import com.oasisfeng.island.mobile.R
16 | import com.oasisfeng.island.settings.IslandNameManager
17 | import com.oasisfeng.island.util.Users
18 | import com.oasisfeng.island.util.toId
19 |
20 | class MainViewModel(app: Application, state: SavedStateHandle): AppListViewModel(app, state) {
21 |
22 | private val mProfileStates = LiveProfileStates(app)
23 |
24 | fun initializeTabs(activity: FragmentActivity, tabs: TabLayout) {
25 | tabs.tabIconTint = null
26 | tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
27 | override fun onTabSelected(tab: TabLayout.Tab) = onTabSwitched(activity, tabs, tab)
28 | override fun onTabUnselected(tab: TabLayout.Tab) {}
29 | override fun onTabReselected(tab: TabLayout.Tab) {}})
30 |
31 | // Tab "Discovery" and "Mainland" are always present
32 | tabs.addTab(tabs.newTab().setText(R.string.tab_discovery),/* selected = */false)
33 | val currentProfile = currentProfile
34 | tabs.addTab(tabs.newTab().setText(R.string.tab_mainland),/* selected = */Users.owner == currentProfile)
35 |
36 | for ((profile, name) in IslandNameManager.getAllNames(activity)) {
37 | val tab = tabs.newTab().setTag(profile).setText(name)
38 | mProfileStates.get(profile).observe(activity, { updateTabIconForProfileState(activity, tab, profile, it) })
39 | tabs.addTab(tab,/* selected = */profile == currentProfile) }
40 | }
41 | }
42 |
43 | private fun updateTabIconForProfileState(context: Context, tab: TabLayout.Tab, profile: UserHandle, state: ProfileState) {
44 | Log.d(TAG, "Update tab icon for profile ${profile.toId()}: $state")
45 | val icon = context.getDrawable(R.drawable.ic_island_black_24dp)!!
46 | icon.setTint(context.getColor(if (state == ProfileState.UNAVAILABLE) R.color.state_frozen else R.color.state_alive))
47 | try { tab.icon = context.packageManager.getUserBadgedIcon(icon, profile) }
48 | // (Mostly "vivo" devices before Android Q) "SecurityException: You need MANAGE_USERS permission to: check if specified user a managed profile outside your profile group"
49 | catch (e: SecurityException) { if (SDK_INT >= Q) analytics().logAndReport(TAG, "Error getting user badged icon", e) }
50 | }
51 |
52 | private const val TAG = "Island.MVM"
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/security/SecurityPrompt.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.security;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.widget.Toast;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.StringRes;
10 | import androidx.biometric.BiometricPrompt;
11 | import androidx.fragment.app.FragmentActivity;
12 |
13 | import com.oasisfeng.island.mobile.R;
14 |
15 | /**
16 | * Security protection based on biometric or lock-screen credentials.
17 | *
18 | * Created by Oasis on 2019-1-17.
19 | */
20 | public class SecurityPrompt {
21 |
22 | public static void showBiometricPrompt(final FragmentActivity activity, final @StringRes int title,
23 | final @StringRes int description, final Runnable on_authenticated) {
24 | final Handler handler = new Handler(Looper.getMainLooper());
25 | final Context app_context = activity.getApplication();
26 | new BiometricPrompt(activity, handler::post, new BiometricPrompt.AuthenticationCallback() {
27 |
28 | @Override public void onAuthenticationSucceeded(@NonNull final BiometricPrompt.AuthenticationResult result) {
29 | on_authenticated.run();
30 | }
31 |
32 | @Override public void onAuthenticationError(final int error, @NonNull final CharSequence error_message) {
33 | if (error == BiometricPrompt.ERROR_CANCELED | error == BiometricPrompt.ERROR_NEGATIVE_BUTTON) return;
34 | Toast.makeText(app_context, error_message, Toast.LENGTH_LONG).show();
35 | // if (error == BiometricPrompt.ERROR_TIMEOUT) return;
36 | //
37 | // TODO: Security confirmation with UserManager.createUserCreationIntent()
38 | }
39 |
40 | @Override public void onAuthenticationFailed() {
41 | Toast.makeText(app_context, R.string.toast_security_confirmation_failure, Toast.LENGTH_LONG).show();
42 | }
43 | }).authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(activity.getText(title))
44 | .setDescription(activity.getText(description)).setNegativeButtonText(activity.getText(android.R.string.cancel)).build());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/settings/Preferences.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("DEPRECATION")
2 |
3 | package com.oasisfeng.island.settings
4 |
5 | import android.os.Build.VERSION.SDK_INT
6 | import android.os.Build.VERSION_CODES.O
7 | import android.preference.*
8 | import androidx.annotation.StringRes
9 |
10 | inline fun <T: Preference> PreferenceFragment.setup(@StringRes key: Int, crossinline block: T.() -> Unit)
11 | = @Suppress("UNCHECKED_CAST") (findPreference(getString(key)) as? T)?.apply { block() }
12 |
13 | @Suppress("DEPRECATION") fun PreferenceFragment.remove(preference: Preference) {
14 | if (SDK_INT >= O) preference.parent?.apply { removePreference(preference); if (this is PreferenceCategory && preferenceCount == 0) remove(this) }
15 | else removeLeafPreference(preferenceScreen, preference)
16 | }
17 |
18 | fun removeLeafPreference(root: PreferenceGroup, preference: Preference): Boolean {
19 | if (root.removePreference(preference)) return true
20 | for (i in 0 until root.preferenceCount) {
21 | val child = root.getPreference(i)
22 | if (child is PreferenceGroup && removeLeafPreference(child, preference)) {
23 | if (child is PreferenceCategory && child.preferenceCount == 0) root.removePreference(child)
24 | return true
25 | }
26 | }
27 | return false
28 | }
29 |
30 | fun Preference.onClick(block: Preference.() -> Unit) { onPreferenceClickListener = Preference.OnPreferenceClickListener { block(); true }}
31 | inline fun TwoStatePreference.onChange(crossinline block: (Boolean) -> Boolean) = setOnPreferenceChangeListener { _, v -> block(v as Boolean) }
32 | inline fun EditTextPreference.onChange(crossinline block: (String) -> Boolean) = setOnPreferenceChangeListener { _, v -> block(v as String) }
33 | fun TwoStatePreference.lock(checked: Boolean) { isChecked = checked; isSelectable = false }
34 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/setup/SetupActivity.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.setup;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | import com.android.setupwizardlib.util.SystemBarHelper;
7 | import com.oasisfeng.island.mobile.R;
8 |
9 | /**
10 | * Island setup wizard
11 | *
12 | * Created by Oasis on 2016/9/13.
13 | */
14 | public class SetupActivity extends Activity {
15 |
16 | @Override protected void onCreate(final Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | // getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
19 | SystemBarHelper.hideSystemBars(getWindow());
20 | setContentView(R.layout.activity_main);
21 | if (savedInstanceState == null)
22 | getFragmentManager().beginTransaction().replace(R.id.container, new SetupWizardFragment()).commit();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/util/FloatingActionButtonBehavior.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import com.google.android.material.bottomsheet.BottomSheetBehavior;
9 | import com.google.android.material.floatingactionbutton.FloatingActionButton;
10 | import com.google.android.material.snackbar.Snackbar;
11 |
12 | import androidx.coordinatorlayout.widget.CoordinatorLayout;
13 |
14 | /**
15 | * Expand the default behavior of FloatingActionButton to support {@link BottomSheetBehavior}
16 | *
17 | * Created by Oasis on 2016/6/24.
18 | */
19 | public class FloatingActionButtonBehavior extends FloatingActionButton.Behavior {
20 |
21 | @Override public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionButton child, final View dependency) {
22 | return super.layoutDependsOn(parent, child, dependency) || isBottomSheet(dependency);
23 | }
24 |
25 | @Override public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionButton child, final View dependency) {
26 | // Block parent behavior for SnackBar if bottom sheet is visible
27 | if (dependency instanceof Snackbar.SnackbarLayout) {
28 | final ViewGroup.LayoutParams fab_general_params = child.getLayoutParams();
29 | if (fab_general_params instanceof CoordinatorLayout.LayoutParams) {
30 | final CoordinatorLayout.LayoutParams fab_params = ((CoordinatorLayout.LayoutParams) fab_general_params);
31 | final int anchor_id = fab_params.getAnchorId();
32 | if (anchor_id != 0) {
33 | final View anchor = parent.findViewById(anchor_id);
34 | if (anchor != null && anchor.getVisibility() == View.VISIBLE) return false;
35 | }
36 | }
37 | }
38 | return super.onDependentViewChanged(parent, child, dependency);
39 | }
40 |
41 | private boolean isBottomSheet(final View view) {
42 | final ViewGroup.LayoutParams params = view.getLayoutParams();
43 | return params instanceof CoordinatorLayout.LayoutParams
44 | && ((CoordinatorLayout.LayoutParams) params).getBehavior() instanceof BottomSheetBehavior;
45 | }
46 |
47 | public FloatingActionButtonBehavior() {}
48 | public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {}
49 | }
50 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/util/SimpleAsyncTask.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.os.AsyncTask;
4 | import androidx.annotation.WorkerThread;
5 |
6 | /**
7 | * Simplified {@link AsyncTask} without parameters and result.
8 | *
9 | * Created by Oasis on 2016/11/6.
10 | */
11 | public abstract class SimpleAsyncTask extends AsyncTask<Void, Void, Void> {
12 |
13 | public static void execute(final Runnable do_in_background, final Runnable on_post_execute) {
14 | new SimpleAsyncTask() {
15 | @Override protected void doInBackground() {
16 | do_in_background.run();
17 | }
18 |
19 | @Override protected void onPostExecute() {
20 | on_post_execute.run();
21 | }
22 | }.execute();
23 | }
24 |
25 | protected abstract void doInBackground();
26 | protected abstract void onPostExecute();
27 |
28 | @Override @WorkerThread protected final Void doInBackground(final Void... params) { doInBackground(); return null; }
29 | @Override protected final void onPostExecute(final Void aVoid) { onPostExecute(); }
30 | }
31 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/util/TextFormat.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.content.Context;
4 | import android.text.Html;
5 | import android.text.SpannedString;
6 |
7 | import androidx.annotation.Nullable;
8 | import androidx.annotation.StringRes;
9 |
10 | /**
11 | * Utility class for text format.
12 | *
13 | * Created by Oasis on 2017/8/28.
14 | */
15 | public class TextFormat {
16 |
17 | public static @Nullable CharSequence getText(final Context context, final @StringRes int text, final Object... args) {
18 | return text == 0 ? null : args == null || args.length == 0 ? context.getText(text)
19 | : Html.fromHtml(String.format(Html.toHtml(new SpannedString(context.getText(text))), args));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/island/widget/PersistableSearchView.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.widget;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.SearchView;
6 |
7 | import androidx.annotation.Nullable;
8 |
9 | /**
10 | * Do not reset query text upon expanding and collapsing.
11 | *
12 | * Created by Oasis on 2019-6-30.
13 | */
14 | public class PersistableSearchView extends SearchView {
15 |
16 | @Override public void onActionViewExpanded() {
17 | super.setOnQueryTextListener(null); // onActionViewExpanded() does not call setQuery()
18 | super.onActionViewExpanded();
19 | super.setOnQueryTextListener(mOnQueryTextListener);
20 | }
21 |
22 | @Override public void onActionViewCollapsed() {
23 | mBlockSetQuery = true;
24 | super.onActionViewCollapsed();
25 | mBlockSetQuery = false;
26 | if (mOnCloseListener != null) mOnCloseListener.onClose();
27 | }
28 |
29 | @Override public void setQuery(final CharSequence query, final boolean submit) {
30 | if (mBlockSetQuery) mBlockSetQuery = false;
31 | else super.setQuery(query, submit);
32 | }
33 |
34 | @Override public void setOnCloseListener(final OnCloseListener listener) { super.setOnCloseListener(mOnCloseListener = listener); }
35 | @Override public void setOnQueryTextListener(final OnQueryTextListener listener) { super.setOnQueryTextListener(mOnQueryTextListener = listener); }
36 |
37 | public PersistableSearchView(final Context context) { super(context); }
38 | public PersistableSearchView(final Context context, final AttributeSet attrs) { super(context, attrs); }
39 | public PersistableSearchView(final Context context, final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); }
40 | @SuppressWarnings("unused") public PersistableSearchView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
41 | super(context, attrs, defStyleAttr, defStyleRes);
42 | }
43 |
44 | private boolean mBlockSetQuery;
45 | private @Nullable OnCloseListener mOnCloseListener;
46 | private @Nullable OnQueryTextListener mOnQueryTextListener;
47 | }
48 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/settings/ActionButtonPreference.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.settings;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ImageView;
7 |
8 | import com.oasisfeng.island.mobile.R;
9 |
10 | import androidx.annotation.DrawableRes;
11 | import androidx.annotation.StringRes;
12 |
13 | /** A preference with a clickable action icon on the side */
14 | public class ActionButtonPreference extends AdvancedPreference implements View.OnClickListener {
15 |
16 | public void setSummaryAndActionButton(final @StringRes int summary, final @DrawableRes int icon, final OnPreferenceClickListener listener) {
17 | setSummaryAndNotSelectable(summary);
18 | mActionIcon = icon;
19 | mOnActionClickListener = listener;
20 | notifyChanged();
21 | }
22 |
23 | @Override protected void onBindView(final View view) {
24 | super.onBindView(view);
25 | final ImageView icon = (ImageView) view.findViewById(R.id.preference_action_button);
26 | if (mActionIcon != 0) {
27 | icon.setImageDrawable(getContext().getDrawable(mActionIcon));
28 | icon.setOnClickListener(this);
29 | icon.setEnabled(true); // Make icon available even if the preference itself is disabled.
30 | icon.setVisibility(View.VISIBLE);
31 | } else icon.setVisibility(View.GONE);
32 | }
33 |
34 | @Override public void onClick(final View v) {
35 | if (v.getId() == R.id.preference_action_button)
36 | if (mOnActionClickListener != null)
37 | mOnActionClickListener.onPreferenceClick(this);
38 | }
39 |
40 | public ActionButtonPreference(final Context context, final AttributeSet attrs) {
41 | super(context, attrs);
42 | mActionIcon = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res-auto", "icon", 0);
43 | setWidgetLayoutResource(R.layout.preference_widget_action_button);
44 | }
45 |
46 | private @DrawableRes int mActionIcon;
47 | private OnPreferenceClickListener mOnActionClickListener;
48 | }
49 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/settings/AdvancedPreference.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.settings;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.preference.Preference;
7 | import android.util.AttributeSet;
8 |
9 | import androidx.annotation.StringRes;
10 |
11 | /**
12 | * Improved {@link Preference}
13 | *
14 | * Created by Oasis on 2017/3/7.
15 | */
16 | public class AdvancedPreference extends Preference {
17 |
18 | private static final String PACKAGE_PLACEHOLDER_SELF = "self";
19 |
20 | @Override public void setIntent(final Intent intent) {
21 | final ComponentName component = intent.getComponent();
22 | if (component != null && PACKAGE_PLACEHOLDER_SELF.equals(component.getPackageName())) {
23 | if (component.getClassName().isEmpty()) intent.setPackage(getContext().getPackageName()).setComponent(null);
24 | else intent.setComponent(new ComponentName(getContext().getPackageName(), component.getClassName()));
25 | }
26 | super.setIntent(intent);
27 | }
28 |
29 | public void setSummaryAndNotSelectable(final @StringRes int summary) {
30 | setSummary(summary);
31 | setSelectable(false);
32 | }
33 |
34 | public void setSummaryAndClickListener(final @StringRes int summary, final OnPreferenceClickListener listener) {
35 | setSummary(summary);
36 | setSelectable(true);
37 | setOnPreferenceClickListener(listener);
38 | }
39 |
40 | public AdvancedPreference(final Context context, final AttributeSet attrs) {
41 | super(context, attrs);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/oasisfeng/ui/card/CardViewModel.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.ui.card;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | import com.oasisfeng.island.mobile.R;
7 |
8 | import androidx.annotation.ColorInt;
9 | import androidx.annotation.StringRes;
10 | import androidx.cardview.widget.CardView;
11 | import androidx.core.content.ContextCompat;
12 |
13 | /**
14 | * View-model for card
15 | *
16 | * Created by Oasis on 2017/9/7.
17 | */
18 | public class CardViewModel {
19 |
20 | public CharSequence title;
21 | public CharSequence text;
22 | public CharSequence button_start;
23 | public CharSequence button_end;
24 | public @ColorInt int color;
25 | public boolean dismissible = true;
26 |
27 | protected CardViewModel(final Context context, final @StringRes int title, final @StringRes int text,
28 | final @StringRes int button_start, final @StringRes int button_end) {
29 | this.title = title == 0 ? null : context.getText(title);
30 | this.text = text == 0 ? null : context.getText(text);
31 | this.button_start = button_start == 0 ? null : context.getText(button_start);
32 | this.button_end = button_end == 0 ? null : context.getText(button_end);
33 | color = ContextCompat.getColor(context, R.color.card_attention);
34 | }
35 |
36 | @SuppressWarnings("MethodMayBeStatic") protected void dismiss(final CardView card) {
37 | card.setVisibility(View.GONE);
38 | }
39 |
40 | public void onButtonStartClick(final Context context, final CardView card) {}
41 | public void onButtonEndClick(final Context context, final CardView card) {}
42 | }
43 |
--------------------------------------------------------------------------------
/mobile/src/main/res/animator/slide_back_in.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <set xmlns:android="http://schemas.android.com/apk/res/android">
3 | <objectAnimator android:propertyName="xFraction"
4 | android:valueFrom="-1.0"
5 | android:valueTo="0"
6 | android:duration="@integer/suwTransitionDuration"
7 | android:interpolator="@android:anim/linear_interpolator" />
8 | </set>
--------------------------------------------------------------------------------
/mobile/src/main/res/animator/slide_back_out.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <set xmlns:android="http://schemas.android.com/apk/res/android">
3 | <objectAnimator android:propertyName="xFraction"
4 | android:valueFrom="0"
5 | android:valueTo="1.0"
6 | android:duration="@integer/suwTransitionDuration"
7 | android:interpolator="@android:anim/linear_interpolator" />
8 | </set>
--------------------------------------------------------------------------------
/mobile/src/main/res/animator/slide_next_in.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <set xmlns:android="http://schemas.android.com/apk/res/android">
3 | <objectAnimator android:propertyName="xFraction"
4 | android:valueFrom="1.0"
5 | android:valueTo="0"
6 | android:duration="@integer/suwTransitionDuration"
7 | android:interpolator="@android:anim/linear_interpolator" />
8 | </set>
--------------------------------------------------------------------------------
/mobile/src/main/res/animator/slide_next_out.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <set xmlns:android="http://schemas.android.com/apk/res/android">
3 | <objectAnimator android:propertyName="xFraction"
4 | android:valueFrom="0"
5 | android:valueTo="-1.0"
6 | android:duration="@integer/suwTransitionDuration"
7 | android:interpolator="@android:anim/linear_interpolator" />
8 | </set>
--------------------------------------------------------------------------------
/mobile/src/main/res/color/chip_background_color.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 |
4 | <item android:color="@color/accent" android:state_enabled="true" android:state_selected="true"/>
5 | <item android:color="@color/background" android:state_enabled="true"/>
6 |
7 | </selector>
8 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-nodpi/ic_launcher_appops.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_appops.webp
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-nodpi/ic_launcher_fx.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_fx.webp
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-nodpi/ic_launcher_greenify.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_greenify.webp
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-nodpi/ic_launcher_saf_enhancer.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_saf_enhancer.webp
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-xxxhdpi/setup_header.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-xxxhdpi/setup_header.9.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/app_icon_empty.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <shape xmlns:android="http://schemas.android.com/apk/res/android">
3 | <size android:width="@dimen/app_icon_size" android:height="@dimen/app_icon_size" />
4 | </shape>
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/bottom_navigation_text_color.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:color="@android:color/white" android:state_checked="true" />
4 | <item android:color="#bebebe" />
5 | </selector>
6 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_add_to_photos_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FFFFFFFF"
8 | android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_explore_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FF000000"
8 | android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_filter_list_white_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector android:height="24dp" android:tint="#FFFFFF"
2 | android:viewportHeight="24.0" android:viewportWidth="24.0"
3 | android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 | <path android:fillColor="#FF000000" android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
5 | </vector>
6 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_info_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportHeight="24.0"
5 | android:viewportWidth="24.0">
6 | <path
7 | android:fillColor="@color/action_icon"
8 | android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_island_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FF000000"
8 | android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_launch_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0"
6 | android:autoMirrored="true">
7 | <path
8 | android:fillColor="@color/side_control"
9 | android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
10 | </vector>
11 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_lightbulb_outline_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FFFFFFFF"
8 | android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_lock_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FFFFFFFF"
8 | android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_mainland_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FF000000"
8 | android:pathData="M23,18V6c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2zM8.5,12.5l2.5,3.01L14.5,11l4.5,6H5l3.5,-4.5z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_search_white_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FFFFFFFF"
8 | android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_settings_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="@color/action_icon"
8 | android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_unlock_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="#FFFFFFFF"
8 | android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 | xmlns:tools="http://schemas.android.com/tools"
4 | android:id="@+id/container"
5 | android:layout_width="match_parent"
6 | android:layout_height="match_parent"
7 | tools:context="com.oasisfeng.island.MainActivity"
8 | tools:ignore="MergeRootFrame" />
9 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/preference_widget_action_button.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:layout_width="wrap_content"
4 | android:layout_height="wrap_content"
5 | android:gravity="center_vertical"
6 | android:paddingEnd="?android:attr/scrollbarSize">
7 |
8 | <ImageView
9 | android:id="@+id/preference_action_button"
10 | android:tint="?android:attr/colorControlNormal"
11 | android:layout_width="wrap_content"
12 | android:layout_height="wrap_content"
13 | android:layout_gravity="center_vertical"
14 | android:padding="8dip"
15 | android:background="?android:attr/selectableItemBackground" />
16 |
17 | </LinearLayout>
18 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/setup_wizard.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"
3 | xmlns:bind="http://schemas.android.com/apk/res-auto"
4 | xmlns:app="http://schemas.android.com/apk/res-auto"
5 | xmlns:tools="http://schemas.android.com/tools"
6 | tools:context=".setup.SetupActivity">
7 |
8 | <data>
9 | <import type="android.view.View" />
10 | <import type="com.oasisfeng.island.util.TextFormat" />
11 | <import type="android.text.SpannedString" />
12 | <variable name="setup" type="com.oasisfeng.island.setup.SetupViewModel" />
13 | </data>
14 |
15 | <com.android.setupwizardlib.SetupWizardLayout
16 | android:id="@+id/setup_wizard_layout"
17 | android:layout_width="match_parent"
18 | android:layout_height="match_parent"
19 | style="@style/SetupIllustrationTheme"
20 | app:suwHeaderText="@string/setup_profile_welcome" >
21 |
22 | <!-- Intro -->
23 | <TextView
24 | android:id="@+id/intro"
25 | android:layout_width="match_parent"
26 | android:layout_height="wrap_content"
27 | bind:shown="@{setup.message == 0}"
28 | android:text="@string/setup_island_intro"
29 | style="@style/contentContainer" />
30 |
31 | <!-- Error message (abort) -->
32 | <LinearLayout
33 | android:orientation="vertical"
34 | android:layout_width="match_parent"
35 | android:layout_height="match_parent"
36 | style="@style/contentContainer"
37 | bind:shown="@{setup.message != 0}"
38 | tools:visibility="gone">
39 |
40 | <TextView
41 | android:layout_width="match_parent"
42 | android:layout_height="wrap_content"
43 | style="@style/containerText"
44 | android:text="@{TextFormat.getText(context, setup.message, setup.message_params)}"/>
45 |
46 | <TextView
47 | android:layout_width="match_parent"
48 | android:layout_height="wrap_content"
49 | android:paddingTop="16sp"
50 | style="@style/containerText"
51 | android:text="@string/dialog_manual_setup"
52 | bind:shown="@{setup.action_extra == com.oasisfeng.island.mobile.R.string.button_instructions_online}"/>
53 |
54 | <Button
55 | android:layout_width="wrap_content"
56 | android:layout_height="wrap_content"
57 | android:paddingTop="16sp"
58 | style="@style/AppTheme.BorderlessActionButton"
59 | bind:shown="@{setup.action_extra != 0}"
60 | bind:text="@{setup.action_extra}"
61 | android:onClick="@{v -> setup.onExtraButtonClick(v)}"/>
62 | </LinearLayout>
63 |
64 | </com.android.setupwizardlib.SetupWizardLayout>
65 |
66 | </layout>
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/app_actions.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <menu xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" tools:ignore="AppCompatResource" >
3 | <item android:id="@+id/menu_freeze" android:showAsAction="ifRoom|withText" android:title="@string/action_freeze" android:icon="@drawable/ic_lock_24dp" />
4 | <item android:id="@+id/menu_unfreeze" android:showAsAction="ifRoom|withText" android:title="@string/action_unfreeze" android:icon="@drawable/ic_unlock_24dp" />
5 | <item android:id="@+id/menu_clone" android:showAsAction="ifRoom|withText" android:title="@string/action_clone" android:icon="@drawable/ic_add_to_photos_24dp" />
6 |
7 | <item android:id="@+id/menu_suspend" android:title="Suspend" android:showAsAction="never" android:visible="false" />
8 |
9 | <item android:id="@+id/menu_app_settings" android:showAsAction="ifRoom" android:title="@string/action_app_settings" android:icon="@drawable/ic_settings_applications_white_24dp" />
10 |
11 | <item android:id="@+id/menu_greenify" android:title="@string/action_greenify" android:showAsAction="never" />
12 | <item android:id="@+id/menu_shortcut" android:title="@string/action_shortcut" android:showAsAction="never" />
13 | <item android:id="@+id/menu_remove" android:title="@string/action_remove" android:showAsAction="never" />
14 | <item android:id="@+id/menu_uninstall" android:title="@string/action_uninstall" android:showAsAction="never" />
15 | <item android:id="@+id/menu_reinstall" android:title="@string/action_reinstall" android:showAsAction="ifRoom" />
16 | </menu>
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/main_actions.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="AppCompatResource">
3 | <item android:id="@+id/menu_tip" android:title="@string/menu_tip" android:icon="@drawable/ic_lightbulb_outline_24dp" android:showAsAction="ifRoom" />
4 | <item android:id="@+id/menu_search" android:title="@string/menu_search" android:icon="@drawable/ic_search_white_24dp"
5 | android:showAsAction="collapseActionView|ifRoom" android:actionViewClass="com.oasisfeng.island.widget.PersistableSearchView" />
6 | <item android:id="@+id/menu_filter" android:title="@string/menu_filter" android:icon="@drawable/ic_filter_list_white_24dp" android:showAsAction="ifRoom" />
7 | <item android:id="@+id/menu_settings" android:title="@string/menu_settings" android:showAsAction="never" />
8 | <item android:id="@+id/menu_test" android:title="Test" tools:ignore="HardcodedText" android:showAsAction="never" android:visible="false" />
9 | </menu>
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/pref_island_actions.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <menu xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" tools:ignore="AppCompatResource">
3 | <item android:id="@+id/menu_rename" android:title="@string/action_rename" android:showAsAction="never" />
4 | <item android:id="@+id/menu_test" android:title="Test" android:showAsAction="never" android:visible="false" tools:ignore="HardcodedText" />
5 | </menu>
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 | <background android:drawable="@mipmap/ic_launcher_background"/>
4 | <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
5 | </adaptive-icon>
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 | <background android:drawable="@mipmap/ic_launcher_background"/>
4 | <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
5 | </adaptive-icon>
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Semantic colors -->
4 | <color name="textPrimary">#5C6BC0</color>
5 | <color name="background">#121212</color>
6 | <color name="header_background">#121212</color>
7 | <color name="background_highlight">#2C2C2C</color> <!-- background + 16% white -->
8 | <color name="card_background">#2C2C2C</color>
9 | <color name="card_attention">#635732</color>
10 | <color name="card_button">#259B24</color>
11 | <!-- Specific colors -->
12 | <color name="state_alive">#DD6F00</color>
13 | <color name="state_frozen">#536dfe</color> <!-- Indigo A400 -->
14 | <color name="state_other">@android:color/darker_gray</color>
15 | <color name="side_control">#536dfe</color>
16 | </resources>
17 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Semantic colors -->
4 | <color name="textPrimary">@color/primary_dark</color>
5 | <color name="background">#F3F3F3</color>
6 | <color name="header_background">@android:color/white</color>
7 | <color name="background_highlight">@android:color/white</color>
8 | <color name="card_background">@android:color/white</color>
9 | <color name="card_attention">#FFFFE082</color>
10 | <color name="card_button">#259B24</color>
11 | <!-- Specific colors -->
12 | <color name="state_alive">#FF8F00</color>
13 | <color name="state_frozen">@color/primary_dark</color>
14 | <color name="state_other">@android:color/black</color>
15 | <color name="side_control">@color/primary_dark</color>
16 | </resources>
17 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/constants.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="default_launch_shortcut_prefix" translatable="false">❄</string>
4 | </resources>
--------------------------------------------------------------------------------
/mobile/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources xmlns:tools="http://schemas.android.com/tools">
3 | <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
4 | <dimen name="margin_small">8dp</dimen>
5 | <dimen name="margin_medium">16dp</dimen>
6 | <dimen name="margin_large">32dp</dimen>
7 | <dimen name="elevation_card_resting">2dp</dimen>
8 | <dimen name="elevation_card_raised">8dp</dimen>
9 |
10 | <!-- Semantic definitions -->
11 |
12 | <dimen name="fab_margin">16dp</dimen>
13 | <dimen name="fab_elevation">12dp</dimen>
14 | <dimen name="fab_elevation_pressed">8dp</dimen>
15 |
16 | <dimen name="app_icon_size">36dp</dimen>
17 | <dimen name="card_content_padding">12dp</dimen>
18 |
19 | <!-- Adjust for setup wizard -->
20 | <dimen name="setup_header_height">234dp</dimen>
21 | <dimen name="suw_decor_padding_top">100dp</dimen>
22 |
23 | <!-- Dimens used for the content inside the setup wizard layout. -->
24 | <dimen name="content_padding_bottom">24dp</dimen>
25 | <dimen name="content_padding_start_end">40dp</dimen>
26 | <dimen name="content_padding_top">34dp</dimen>
27 | <dimen name="content_text_size">16sp</dimen>
28 | <dimen name="content_padding_between_text">16dp</dimen>
29 |
30 | <!-- Stretch the BottomNavigationView with only 2 tabs to full width. (480dp is the known max width among large phones) -->
31 | <dimen name="design_bottom_navigation_item_max_width" tools:override="true">240dp</dimen>
32 | <dimen name="design_bottom_navigation_active_item_max_width" tools:override="true">240dp</dimen>
33 | </resources>
34 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <item name="prompt" type="id"/>
4 | </resources>
--------------------------------------------------------------------------------
/mobile/src/main/res/values/preferences.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- General -->
4 | <string translatable="false" name="key_launch_shortcut_prefix">launch_shortcut_prefix</string>
5 | <string translatable="false" name="key_show_admin_message">show_admin_message</string>
6 | <string translatable="false" name="key_preserve_app_ops">preserve_app_ops</string>
7 | <!-- Island (per-profile) -->
8 | <string translatable="false" name="key_island_name">island_name</string>
9 | <string translatable="false" name="key_device_owner_setup">device_owner_setup</string>
10 | <string translatable="false" name="key_privacy">privacy</string>
11 | <string translatable="false" name="key_privacy_appops">appops</string>
12 | <string translatable="false" name="key_manage_read_phone_state">manage_read_phone_state</string>
13 | <string translatable="false" name="key_manage_read_sms">manage_read_sms</string>
14 | <string translatable="false" name="key_manage_location">manage_location</string>
15 | <string translatable="false" name="key_manage_storage">manage_storage</string>
16 | <string translatable="false" name="key_cross_profile">cross_profile</string>
17 | <string translatable="false" name="key_watcher">watcher</string>
18 | <string translatable="false" name="key_island_watcher">island_watcher</string>
19 | <string translatable="false" name="key_app_watcher">app_watcher</string>
20 | <string translatable="false" name="key_setup">setup</string>
21 | <string translatable="false" name="key_reprovision">reprovision</string>
22 | <string translatable="false" name="key_destroy">destroy</string>
23 | <!-- Privacy -->
24 | <string translatable="false" name="key_privacy_notification_for_clone">privacy_notification_clone</string>
25 | <!-- About -->
26 | <string translatable="false" name="key_version">version</string>
27 | </resources>
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/actions.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <actions>
3 | <action intentName="actions.intent.OPEN_APP_FEATURE">
4 | <fulfillment urlTemplate="island://feature/{feature}">
5 | <parameter-mapping intentParameter="feature" urlParameter="feature" />
6 | </fulfillment>
7 | </action>
8 | </actions>
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_about.xml:
--------------------------------------------------------------------------------
1 | <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context="com.oasisfeng.island.settings.SettingsActivity">
2 |
3 | <Preference android:key="@string/key_version" android:title="@string/pref_about_version_title" tools:summary="1.0 beta 1" />
4 |
5 | <Preference android:title="@string/pref_about_open_source_title" android:summary="@string/pref_about_open_source_summary">
6 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/oasisfeng/island" />
7 | </Preference>
8 |
9 | <PreferenceScreen android:title="@string/pref_about_licenses_title">
10 | <PreferenceCategory android:title="@string/pref_about_licenses_title">
11 | <PreferenceScreen android:title="Android Open Source Project" android:summary="Apache License, Version 2.0">
12 | <intent android:action="android.intent.action.VIEW" android:data="https://source.android.com/" />
13 | </PreferenceScreen>
14 | <PreferenceScreen android:title="Deagle Library" android:summary="Apache License, Version 2.0">
15 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/oasisfeng/deagle" />
16 | </PreferenceScreen>
17 | <PreferenceScreen android:title="Stream Support" android:summary="GNU General Public License, version 2, with the Classpath Exception">
18 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/streamsupport/streamsupport" />
19 | </PreferenceScreen>
20 | <PreferenceScreen android:title="Guava" android:summary="Apache License, Version 2.0">
21 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/google/guava" />
22 | </PreferenceScreen>
23 | <PreferenceScreen android:title="libsuperuser" android:summary="Apache License, Version 2.0">
24 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/Chainfire/libsuperuser" />
25 | </PreferenceScreen>
26 | <PreferenceScreen android:title="Material Tap Target Prompt" android:summary="Apache License, Version 2.0">
27 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/sjwall/MaterialTapTargetPrompt" />
28 | </PreferenceScreen>
29 | <PreferenceScreen android:title="APKParser" android:summary="BSD License 2.0">
30 | <intent android:action="android.intent.action.VIEW" android:data="https://github.com/jaredrummler/APKParser" />
31 | </PreferenceScreen>
32 | </PreferenceCategory>
33 | </PreferenceScreen>
34 |
35 | </PreferenceScreen>
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_general.xml:
--------------------------------------------------------------------------------
1 | <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
2 |
3 | <CheckBoxPreference android:key="@string/key_preserve_app_ops"
4 | android:title="@string/pref_preserve_app_ops"
5 | android:summary="@string/pref_preserve_app_ops_description"
6 | android:persistent="false" />
7 |
8 | <SwitchPreference android:key="@string/key_show_admin_message"
9 | android:title="@string/pref_show_admin_message_title"
10 | android:summary="@string/pref_show_admin_message_summary"
11 | android:persistent="false" />
12 |
13 | <PreferenceCategory android:title="@string/pref_cat_app_launch_shortcuts">
14 |
15 | <EditTextPreference android:key="@string/key_launch_shortcut_prefix"
16 | android:title="@string/pref_launch_shortcut_prefix_title"
17 | android:defaultValue="@string/default_launch_shortcut_prefix" />
18 |
19 | <!--<SwitchPreference
20 | android:key="@string/setting_alt_shortcut_badge"
21 | android:title="@string/pref_alt_shortcut_badge_title"
22 | android:summary="@string/pref_alt_shortcut_badge_summary"
23 | android:persistent="false"
24 | android:defaultValue="false" />-->
25 |
26 | <SwitchPreference
27 | android:key="@string/setting_dynamic_shortcut_label"
28 | android:title="@string/pref_dynamic_shortcut_label_title"
29 | android:summary="@string/pref_dynamic_shortcut_label_summary"
30 | android:persistent="false"
31 | android:defaultValue="false" />
32 |
33 | </PreferenceCategory>
34 |
35 | </PreferenceScreen>
36 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_headers.xml:
--------------------------------------------------------------------------------
1 | <preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
2 |
3 | <header
4 | android:fragment="com.oasisfeng.island.settings.GeneralPreferenceFragment"
5 | android:icon="@drawable/ic_settings_black_24dp"
6 | android:title="@string/pref_general_header" />
7 |
8 | <header
9 | android:id="@+id/pref_header_island"
10 | android:fragment="com.oasisfeng.island.settings.IslandSettingsFragment"
11 | android:icon="@drawable/ic_landscape_black_24dp"
12 | android:title="@string/pref_scoped_settings_header"
13 | android:breadCrumbTitle="@string/tab_mainland" /> <!-- "breadCrumbTitle" is only used for mainland -->
14 |
15 | <header
16 | android:fragment="com.oasisfeng.island.settings.SettingsActivity$AboutFragment"
17 | android:icon="@drawable/ic_info_black_24dp"
18 | android:title="@string/pref_about_header" />
19 |
20 | </preference-headers>
21 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <searchable xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:label="@string/label_searchable"
4 | android:includeInGlobalSearch="true" />
--------------------------------------------------------------------------------
/open/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion this.compileSdkVersion
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 | }
10 |
11 | kotlinOptions.jvmTarget = "1.8"
12 |
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | }
18 |
19 | dependencies {
20 | implementation project(':shared')
21 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
22 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
23 | }
24 |
--------------------------------------------------------------------------------
/open/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest package="com.oasisfeng.island.open" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution" xmlns:tools="http://schemas.android.com/tools">
2 |
3 | <application tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
4 |
5 | <service android:name="com.oasisfeng.island.SystemServiceBridge" android:exported="true" android:process=":api"> <!-- Run in separate process to isolate potential global injection -->
6 | <intent-filter>
7 | <action android:name="com.oasisfeng.island.api.action.BIND_SYSTEM_SERVICE" />
8 | <data android:scheme="service" />
9 | </intent-filter>
10 | </service>
11 |
12 | <provider android:name="com.oasisfeng.island.DelegatedScopeAuthorization$Initializer" android:authorities="com.oasisfeng.island.open.initializer" android:exported="false" />
13 | <receiver android:name="com.oasisfeng.island.DelegatedScopeAuthorization" android:permission="android.permission.BIND_DEVICE_ADMIN">
14 | <intent-filter>
15 | <action android:name="android.content.action.REQUEST_PERMISSION" />
16 | </intent-filter>
17 | </receiver>
18 |
19 | </application>
20 | </manifest>
21 |
22 |
--------------------------------------------------------------------------------
/open/src/main/java/android/app/DerivedAppOpsManager.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | /**
4 | * Intermediate class for {@link AppOpsManager} derivation
5 | *
6 | * Created by Oasis on 2019-4-30.
7 | */
8 | public class DerivedAppOpsManager extends AppOpsManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/android/app/admin/DerivedDevicePolicyManager.java:
--------------------------------------------------------------------------------
1 | package android.app.admin;
2 |
3 | /**
4 | * Intermediate class for {@link DevicePolicyManager} derivation
5 | *
6 | * Created by Oasis on 2019-4-28.
7 | */
8 | public class DerivedDevicePolicyManager extends DevicePolicyManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/android/content/DerivedRestrictionsManager.java:
--------------------------------------------------------------------------------
1 | package android.content;
2 |
3 | /**
4 | * Intermediate class for {@link RestrictionsManager} derivation
5 | *
6 | * Created by Oasis on 2019-6-6.
7 | */
8 | public class DerivedRestrictionsManager extends RestrictionsManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/ApiConstants.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.RestrictionsManager;
6 | import android.os.PersistableBundle;
7 | import android.os.UserHandle;
8 |
9 | /**
10 | * API-related constants
11 | *
12 | * Created by Oasis on 2019-6-10.
13 | */
14 | @SuppressLint("InlinedApi") public class ApiConstants {
15 |
16 | /**
17 | * Both as request type in {@link RestrictionsManager#requestPermission(String, String, PersistableBundle)}
18 | * and restriction key in {@link RestrictionsManager#getApplicationRestrictions()} (value: authorized delegations in string array)
19 | */
20 | static final String TYPE_DELEGATION = "com.oasisfeng.island.delegation"; // Name-spaced as requirement mentioned by RestrictionsManager.requestPermission()
21 |
22 | /**
23 | * Optional user serial number to request cross-user authorization. (Without this key, default to current user only)
24 | *
25 | * @see android.os.UserManager#getSerialNumberForUser(UserHandle)
26 | */
27 | public static final String REQUEST_KEY_USER_SERIAL_NUMBER = "com.oasisfeng.island.request.user";
28 |
29 | /** Special value for {@link #REQUEST_KEY_USER_SERIAL_NUMBER}, to apply for all users. */
30 | public static final long USER_ALL = -1;
31 |
32 | public static final String DELEGATION_PACKAGE_ACCESS = DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
33 | public static final String DELEGATION_PERMISSION_GRANT = DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
34 | /* Custom delegations with prefix "-island-" */
35 | public static final String DELEGATION_APP_OPS = "-island-delegation-app-ops";
36 | }
37 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/SystemServiceBridge.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ResolveInfo;
7 | import android.os.IBinder;
8 | import android.os.UserHandle;
9 | import android.util.Log;
10 |
11 | import androidx.annotation.Nullable;
12 |
13 | import com.oasisfeng.island.api.DelegatedAppOpsManager;
14 | import com.oasisfeng.island.api.DelegatedDevicePolicyManager;
15 | import com.oasisfeng.island.api.PermissionForwardingRestrictionsManager;
16 | import com.oasisfeng.island.util.Users;
17 |
18 | import java.util.Objects;
19 |
20 | /**
21 | * Created by Oasis on 2019-6-5.
22 | */
23 | public class SystemServiceBridge extends Service {
24 |
25 | @Nullable @Override public IBinder onBind(final Intent intent) {
26 | Log.v(TAG, "onBind: " + intent);
27 | // This check essentially verifies action and scheme without hardcoded string.
28 | final ResolveInfo service = getPackageManager().resolveService(new Intent(intent.getAction(), intent.getData()).setPackage(getPackageName()), 0);
29 | if (service == null || ! getClass().getName().equals(service.serviceInfo.name)) return null;
30 |
31 | final String ssp = Objects.requireNonNull(intent.getData()).getSchemeSpecificPart();
32 | final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
33 | if (ssp != null) switch (ssp) {
34 | case Context.RESTRICTIONS_SERVICE:
35 | try {
36 | return PermissionForwardingRestrictionsManager.buildBinderProxy(this, user);
37 | } catch (final ReflectiveOperationException e) {
38 | Log.e(TAG, "Error preparing delegated RestrictionsManager", e);
39 | } catch (final PermissionForwardingRestrictionsManager.IncompatibilityException e) {
40 | Log.e(TAG, "Incompatible ROM");
41 | }
42 | break;
43 | case Context.DEVICE_POLICY_SERVICE:
44 | try {
45 | return DelegatedDevicePolicyManager.buildBinderProxy(this);
46 | } catch (final ReflectiveOperationException e) {
47 | Log.e(TAG, "Error preparing delegated DevicePolicyManager", e);
48 | break;
49 | }
50 | case Context.APP_OPS_SERVICE:
51 | try {
52 | return DelegatedAppOpsManager.buildBinderProxy(this);
53 | } catch (final ReflectiveOperationException e) {
54 | Log.e(TAG, "Error preparing delegated AppOpsManager", e);
55 | break;
56 | }
57 |
58 | default: Log.w(TAG, "Unsupported system service: " + ssp);
59 | }
60 | return null;
61 | }
62 |
63 | @Override public void onCreate() {
64 | Users.refreshUsers(this); // Not initialized automatically due to separate process
65 | }
66 |
67 | private static final String TAG = "Island.SSB";
68 | }
69 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/api/DelegatedDevicePolicyManager.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.api;
2 |
3 | import android.app.admin.DerivedDevicePolicyManager;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.Context;
6 | import android.os.Binder;
7 |
8 | import com.oasisfeng.island.ApiConstants;
9 | import com.oasisfeng.island.RestrictedBinderProxy;
10 |
11 | import static android.content.Context.DEVICE_POLICY_SERVICE;
12 |
13 | /**
14 | * Delegated {@link DevicePolicyManager}
15 | *
16 | * Created by Oasis on 2019-4-30.
17 | */
18 | public class DelegatedDevicePolicyManager extends DerivedDevicePolicyManager {
19 |
20 | public static Binder buildBinderProxy(final Context context) throws ReflectiveOperationException {
21 | return new DelegatedDevicePolicyManager(context).mBinderProxy;
22 | }
23 |
24 | private DelegatedDevicePolicyManager(final Context c) throws ReflectiveOperationException {
25 | mBinderProxy = sHelper.inject(this, c, DEVICE_POLICY_SERVICE, ApiConstants.DELEGATION_PACKAGE_ACCESS);
26 |
27 | // Whiltelist supported APIs by invoking them (with dummy arguments) before seal().
28 | setApplicationHidden(null, null, false);
29 | isApplicationHidden(null, null);
30 | mBinderProxy.seal();
31 | }
32 |
33 | private final RestrictedBinderProxy mBinderProxy;
34 |
35 | private static final DerivedManagerHelper<DevicePolicyManager> sHelper = new DerivedManagerHelper<>(DevicePolicyManager.class);
36 | }
37 |
--------------------------------------------------------------------------------
/open/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_delegated_scope_auth_title">Pedido de autorização de %s</string>
4 | <string name="prompt_unblock_notification_for_auth_request">Desbloquear as notificações para mostrar o pedido de autorização</string>
5 | <string name="notification_delegated_scope_auth_text">Permitir para %s, sempre?</string>
6 | <string name="action_authorize">Autorizar</string>
7 | <string name="label_delegation_package_access">Suspender e ativar os apps</string>
8 | <string name="label_delegation_permission_grant">Controlar a permissão do tempo de execução para os apps (incluir suspensão)</string>
9 | <string name="label_delegation_app_ops">Gerenciar App Ops (restrições de permissão)</string>
10 | </resources>
11 |
--------------------------------------------------------------------------------
/open/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_delegated_scope_auth_title">授權請求 來自 %s</string>
4 | <string name="prompt_unblock_notification_for_auth_request">請恢復通知以顯示授權請求</string>
5 | <string name="notification_delegated_scope_auth_text">允許其(在任何时候)%s</string>
6 | <string name="action_authorize">同意</string>
7 | <string name="label_delegation_package_access">凍結及解凍應用程式</string>
8 | <string name="label_delegation_permission_grant">控制應用程式的執行期權限(包括 鎖定)</string>
9 | <string name="label_delegation_app_ops">管理應用程式的權限限制 (App Ops)</string>
10 | </resources>
--------------------------------------------------------------------------------
/open/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_delegated_scope_auth_title">授权请求 来自 %s</string>
4 | <string name="prompt_unblock_notification_for_auth_request">请恢复通知以显示授权请求</string>
5 | <string name="notification_delegated_scope_auth_text">允许其(在任何时候)%s</string>
6 | <string name="action_authorize">同意</string>
7 | <string name="label_delegation_package_access">冻结及解冻应用</string>
8 | <string name="label_delegation_permission_grant">控制应用的运行期权限(包括 锁定)</string>
9 | <string name="label_delegation_app_ops">管理应用的权限限制 (App Ops)</string>
10 | </resources>
--------------------------------------------------------------------------------
/open/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_delegated_scope_auth_title">Authorization request from %s</string>
4 | <string name="prompt_unblock_notification_for_auth_request">Unblock notifications to show authorization request</string>
5 | <string name="notification_delegated_scope_auth_text">Allow it to %s, at any time?</string>
6 | <string name="action_authorize">Authorize</string>
7 | <string name="label_delegation_package_access">freeze and unfreeze apps</string>
8 | <string name="label_delegation_permission_grant">control runtime permission for apps (include locking)</string>
9 | <string name="label_delegation_app_ops">manage app ops (permission restrictions)</string>
10 | </resources>
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'shared', 'engine', 'mobile', 'fileprovider', 'installer', 'watcher', 'open'
2 | include 'assembly'
3 | include ':deagle'; project(':deagle').projectDir = new File(settingsDir, '../deagle/library')
4 |
--------------------------------------------------------------------------------
/shared/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
10 | }
11 |
12 | buildTypes {
13 | release { buildConfigField("boolean", "CRASHLYTICS_ENABLED", "true") }
14 | debug { buildConfigField("boolean", "CRASHLYTICS_ENABLED", "false") }
15 | }
16 |
17 | kotlinOptions.jvmTarget = "1.8"
18 |
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 |
24 | lintOptions {
25 | check 'NewApi'
26 | abortOnError true
27 | xmlReport false
28 | textReport true
29 | textOutput "stdout"
30 | }
31 |
32 | // Enable lint checking in all build variants.
33 | libraryVariants.all { variant ->
34 | variant.outputs.each { output ->
35 | def lintTask = tasks["lint${variant.name.capitalize()}"]
36 | output.assemble.dependsOn lintTask
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 | api project(':deagle')
43 | api 'com.google.code.findbugs:jsr305:3.0.2'
44 | implementation 'org.jetbrains:annotations:15.0'
45 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
46 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
47 | implementation 'androidx.core:core-ktx:1.3.2'
48 | implementation 'com.google.android.gms:play-services-analytics:17.0.0'
49 | implementation 'com.google.firebase:firebase-core:17.5.1'
50 | implementation 'com.google.firebase:firebase-analytics:17.6.0'
51 | implementation 'com.google.firebase:firebase-config:19.2.0'
52 | implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
53 | implementation 'com.oasisfeng.condom:library:2.5.0'
54 | implementation 'com.squareup.okhttp3:okhttp:3.12.1'
55 | implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.10.0'
56 |
57 | testImplementation 'junit:junit:4.12'
58 | testImplementation 'org.mockito:mockito-core:2.18.3'
59 |
60 | androidTestImplementation 'junit:junit:4.12'
61 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutine_version"
62 | androidTestImplementation 'androidx.test:core:1.3.0'
63 | androidTestImplementation 'androidx.test:runner:1.3.0'
64 | androidTestImplementation 'androidx.test:rules:1.3.0'
65 | }
66 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IMethodShuttle.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import com.oasisfeng.island.shuttle.MethodInvocation;
4 |
5 | interface IMethodShuttle {
6 | void invoke(inout MethodInvocation invocation);
7 | }
8 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IServiceConnection.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.ComponentName;
4 | import com.oasisfeng.island.shuttle.IUnbinder;
5 |
6 | interface IServiceConnection {
7 | oneway void onServiceConnected(in ComponentName name, in IBinder service, in IUnbinder unbinder);
8 | oneway void onServiceDisconnected(in ComponentName name);
9 | oneway void onServiceFailed();
10 | }
11 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IUnbinder.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | interface IUnbinder {
4 | boolean unbind();
5 | }
6 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/MethodInvocation.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | parcelable MethodInvocation;
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/IntentCompat.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content;
2 |
3 | import androidx.annotation.RequiresApi;
4 |
5 | import static android.os.Build.VERSION_CODES.O;
6 |
7 | /**
8 | * Created by Oasis on 2018-11-27.
9 | */
10 | public class IntentCompat {
11 |
12 | public static final String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
13 | public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
14 | @RequiresApi(O) public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/pm/ComponentInfo.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content.pm
2 |
3 | import android.content.ComponentName
4 | import android.content.pm.ComponentInfo
5 |
6 | fun ComponentInfo.getComponentName() = ComponentName(packageName, name)
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/pm/PackageManagerUtils.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content.pm
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
6 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
7 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
8 | import android.content.pm.PackageManager.DONT_KILL_APP
9 | import androidx.core.content.ContextCompat
10 |
11 | inline fun <reified T : Any> Context.getSystemService(): T? =
12 | ContextCompat.getSystemService(this, T::class.java)
13 |
14 | inline fun <reified T> Context.disableComponent() =
15 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP)
16 | inline fun <reified T> Context.enableComponent() =
17 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP)
18 | inline fun <reified T> Context.resetComponentState() =
19 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_DEFAULT, DONT_KILL_APP)
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/Config.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
4 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
5 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
6 | import com.oasisfeng.island.firebase.FirebaseWrapper;
7 | import com.oasisfeng.island.shared.BuildConfig;
8 | import com.oasisfeng.island.shared.R;
9 |
10 | /**
11 | * Remotely configurable values, (default values are defined in config_defaults.xml)
12 | *
13 | * Created by Oasis on 2016/5/26.
14 | */
15 | public enum Config {
16 |
17 | /* All keys must be consistent with config_defaults.xml */
18 | IS_REMOTE("is_remote"),
19 | URL_FAQ("url_faq"),
20 | URL_SETUP("url_setup"),
21 | URL_SETUP_GOD_MODE("url_setup_god_mode"),
22 | URL_SETUP_TROUBLESHOOTING("url_setup_trouble"),
23 | URL_FILE_SHUTTLE("url_file_shuttle"),
24 | PERMISSION_REQUEST_ALLOWED_APPS("permission_allowed_apps");
25 |
26 | public String get() {
27 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
28 | config.activateFetched();
29 | return config.getString(key);
30 | }
31 |
32 | public static boolean isRemote() {
33 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
34 | config.activateFetched();
35 | final FirebaseRemoteConfigValue value = config.getValue(IS_REMOTE.key);
36 | return value.getSource() == FirebaseRemoteConfig.VALUE_SOURCE_REMOTE;
37 | }
38 |
39 | Config(final String key) { this.key = key; }
40 |
41 | private final String key;
42 |
43 | static {
44 | FirebaseWrapper.init();
45 | final FirebaseRemoteConfigSettings settings = new FirebaseRemoteConfigSettings.Builder().setDeveloperModeEnabled(BuildConfig.DEBUG).build();
46 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
47 | config.setConfigSettings(settings);
48 | config.setDefaults(R.xml.config_defaults);
49 | config.fetch();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/IslandApplication.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.app.Application;
4 |
5 | import com.oasisfeng.island.analytics.CrashReport;
6 |
7 | /**
8 | * For singleton instance purpose only.
9 | *
10 | * Created by Oasis on 2018/1/3.
11 | */
12 | public class IslandApplication extends Application {
13 |
14 | public static Application $() {
15 | return sInstance;
16 | }
17 |
18 | public IslandApplication() {
19 | if (sInstance != null) throw new IllegalStateException("Already initialized");
20 | sInstance = this;
21 | CrashReport.initCrashHandler();
22 | }
23 |
24 | private static IslandApplication sInstance;
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/PersistentService.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island
2 |
3 | object PersistentService {
4 |
5 | const val SERVICE_INTERFACE = "com.oasisfeng.island.PersistentService"
6 | }
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/analytics/Analytics.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.analytics
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.annotation.CheckResult
6 | import androidx.annotation.Size
7 | import com.google.firebase.analytics.FirebaseAnalytics
8 | import com.oasisfeng.island.IslandApplication
9 | import org.intellij.lang.annotations.Pattern
10 |
11 | /**
12 | * Abstraction for analytics service
13 | *
14 | * Created by Oasis on 2016/5/26.
15 | */
16 | interface Analytics {
17 |
18 | interface Event {
19 | @CheckResult fun with(key: Param, value: String?) = withRaw(key.key, value)
20 | @CheckResult fun withRaw(key: String, value: String?): Event
21 | fun send()
22 | }
23 |
24 | enum class Param(@param:Pattern("^[a-zA-Z][a-zA-Z0-9_]*quot;) val key: String) {
25 | ITEM_ID(FirebaseAnalytics.Param.ITEM_ID),
26 | /** ITEM_CATEGORY and ITEM_NAME cannot be used together (limitation in Google Analytics implementation) */
27 | ITEM_NAME(FirebaseAnalytics.Param.ITEM_NAME),
28 | ITEM_CATEGORY(FirebaseAnalytics.Param.ITEM_CATEGORY),
29 | LOCATION(FirebaseAnalytics.Param.LOCATION),
30 | CONTENT(FirebaseAnalytics.Param.CONTENT);
31 | }
32 |
33 | @CheckResult fun event(@Size(min = 1, max = 40) @Pattern("^[a-zA-Z][a-zA-Z0-9_]*quot;) event: String): Event
34 | fun reportEvent(event: String, params: Bundle)
35 | fun trace(key: String, value: String): Analytics
36 | fun trace(key: String, value: Int): Analytics
37 | fun trace(key: String, value: Boolean): Analytics
38 | fun report(t: Throwable)
39 | fun report(message: String, t: Throwable)
40 | fun logAndReport(tag: String, message: String, t: Throwable) { Log.e(tag, message, t); report(message, t) }
41 |
42 | enum class Property(val key: String) {
43 | DeviceOwner("device_owner"),
44 | IslandSetup("island_setup"),
45 | EncryptionRequired("encryption_required"),
46 | DeviceEncrypted("device_encrypted"),
47 | RemoteConfigAvailable("remote_config_avail"),
48 | FileShuttleEnabled("file_shuttle_enabled");
49 |
50 | }
51 |
52 | fun setProperty(property: Property, @Size(max = 36) value: String)
53 | fun setProperty(property: Property, value: Boolean): Analytics = this.also { setProperty(property, value.toString()) }
54 |
55 | companion object {
56 | @JvmStatic fun log(tag: String, message: String) { Log.i(tag, message); CrashReport.log("[$tag] $message") }
57 | @JvmStatic @Suppress("FunctionName") fun `
() = impl
58 | operator fun invoke() = impl
59 | private val impl: Analytics = AnalyticsImpl(IslandApplication.`
())
60 | }
61 | }
62 |
63 | fun analytics(): Analytics = Analytics()
64 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/analytics/CrashReport.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.analytics;
2 |
3 | import android.os.Process;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.google.firebase.FirebaseApp;
8 | import com.google.firebase.crashlytics.FirebaseCrashlytics;
9 | import com.oasisfeng.android.util.Suppliers;
10 | import com.oasisfeng.island.IslandApplication;
11 |
12 | import java.util.function.Supplier;
13 |
14 |
15 | /**
16 | * Lazy initializer for crash handler.
17 | *
18 | * Created by Oasis on 2017/7/14.
19 | */
20 | public abstract class CrashReport {
21 |
22 | static void logException(final Throwable t) { sSingleton.get().recordException(t); }
23 | static void log(final String message) { sSingleton.get().log(message); }
24 | static void setProperty(final String key, final String value) { sSingleton.get().setCustomKey(key, value); }
25 | static void setProperty(final String key, final int value) { sSingleton.get().setCustomKey(key, value); }
26 | static void setProperty(final String key, final boolean value) { sSingleton.get().setCustomKey(key, value); }
27 |
28 | private static final Supplier<FirebaseCrashlytics> sSingleton = Suppliers.memoize(() -> {
29 | FirebaseApp.initializeApp(IslandApplication.$());
30 | final FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
31 | crashlytics.setCrashlyticsCollectionEnabled(true/*BuildConfig.CRASHLYTICS_ENABLED*/);
32 | crashlytics.setCustomKey("user", Process.myUserHandle().hashCode()); // Attach the current (Android) user ID to crash report.
33 | return crashlytics;
34 | });
35 |
36 | public static void initCrashHandler() {
37 | final Thread.UncaughtExceptionHandler current_exception_handler = Thread.getDefaultUncaughtExceptionHandler();
38 | if (! (current_exception_handler instanceof LazyThreadExceptionHandler))
39 | Thread.setDefaultUncaughtExceptionHandler(new LazyThreadExceptionHandler(current_exception_handler));
40 | }
41 |
42 | private static class LazyThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
43 |
44 | @Override public void uncaughtException(final @NonNull Thread thread, final @NonNull Throwable e) {
45 | if (mHandlingUncaughtException) { // Avoid infinite recursion
46 | mOriginalHandler.uncaughtException(thread, e);
47 | return;
48 | }
49 | mHandlingUncaughtException = true;
50 |
51 | sSingleton.get(); // Initialize if not yet
52 |
53 | final Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
54 | if (handler != null) handler.uncaughtException(thread, e); // May re-enter this method if delegate is initialized above.
55 | mHandlingUncaughtException = false;
56 | }
57 |
58 | LazyThreadExceptionHandler(final Thread.UncaughtExceptionHandler default_handler) {
59 | mOriginalHandler = default_handler;
60 | }
61 |
62 | private final Thread.UncaughtExceptionHandler mOriginalHandler;
63 | private boolean mHandlingUncaughtException;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/data/helper/ApplicationInfoExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.data.helper
2 |
3 | import android.content.pm.ApplicationInfo
4 | import com.oasisfeng.island.util.Hacks
5 |
6 | inline val ApplicationInfo.installed: Boolean
7 | get() = flags and ApplicationInfo.FLAG_INSTALLED != 0
8 |
9 | inline val ApplicationInfo.isSystem: Boolean
10 | get() = flags and ApplicationInfo.FLAG_SYSTEM != 0
11 |
12 | inline val ApplicationInfo.suspended: Boolean
13 | get() = flags and ApplicationInfo.FLAG_SUSPENDED != 0
14 |
15 | inline val ApplicationInfo.hidden: Boolean
16 | get() = Hacks.ApplicationInfo_privateFlags[this]?.and(PRIVATE_FLAG_HIDDEN) == PRIVATE_FLAG_HIDDEN
17 |
18 | const val PRIVATE_FLAG_HIDDEN = 1
19 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/engine/CrossProfile.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.engine
2 |
3 | import android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
4 | import android.content.*
5 | import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
6 | import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
7 | import android.content.pm.ResolveInfo
8 | import com.oasisfeng.island.util.DevicePolicies
9 | import com.oasisfeng.island.util.Users
10 |
11 | object CrossProfile {
12 |
13 | const val CATEGORY_PARENT_PROFILE = "com.oasisfeng.island.category.PARENT_PROFILE"
14 | const val CATEGORY_MANAGED_PROFILE = "com.oasisfeng.island.category.MANAGED_PROFILE"
15 |
16 | /** The target activity must declare [CATEGORY_PARENT_PROFILE] and [Intent.CATEGORY_DEFAULT] in its intent-filter */
17 | @JvmStatic fun decorateIntentForActivityInParentProfile(context: Context, intent: Intent) {
18 | require(intent.data == null) { "Intent with data is not supported yet" }
19 | check(! Users.isOwner()) { "Must not be called in parent profile" }
20 | intent.addCategory(CATEGORY_PARENT_PROFILE)
21 | val candidates = queryActivities(context, intent)
22 | if (candidates.isEmpty()) throw ActivityNotFoundException("No matched activity for $intent")
23 | val forwarder = candidates.findForwarder()
24 | ?: (addRequiredForwarding(context, intent).let { queryActivities(context, intent).findForwarder() }
25 | ?: throw IllegalStateException("Failed to forward $intent"))
26 | intent.component = forwarder.activityInfo.run { ComponentName(packageName, name) }
27 | }
28 |
29 | private fun addRequiredForwarding(context: Context, intent: Intent) = DevicePolicies(context).addCrossProfileIntentFilter(
30 | IntentFilter(intent.action).apply { addCategory(CATEGORY_PARENT_PROFILE) }, FLAG_PARENT_CAN_ACCESS_MANAGED)
31 |
32 | private fun queryActivities(context: Context, intent: Intent)
33 | = context.packageManager.queryIntentActivities(intent, MATCH_DISABLED_COMPONENTS or MATCH_DEFAULT_ONLY) // Probably disabled in current profile
34 |
35 | private fun List<ResolveInfo>.findForwarder()
36 | = firstOrNull { it.activityInfo.packageName == "android" }//?.activityInfo?.run { ComponentName(packageName, name) }
37 | }
38 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/engine/common/WellKnownPackages.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.engine.common;
2 |
3 | /**
4 | * Information about well known packages.
5 | *
6 | * Created by Oasis on 2017/3/11.
7 | */
8 | public class WellKnownPackages {
9 | public static final String PACKAGE_GOOGLE_PLAY_SERVICES = "com.google.android.gms";
10 | public static final String PACKAGE_GOOGLE_PLAY_STORE = "com.android.vending";
11 | public static final String PACKAGE_GOOGLE_CHROME = "com.android.chrome";
12 | }
13 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/installer/InstallerExtras.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.installer;
2 |
3 | /**
4 | * Internal interface of App Installer
5 | *
6 | * Created by Oasis on 2018-11-20.
7 | */
8 | public class InstallerExtras {
9 |
10 | public static final String EXTRA_APP_INFO = "appInfo"; // TODO: Migrate to ClipData-based splits API.
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/notification/NotificationIds.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.notification
2 |
3 | import android.app.Notification
4 | import android.content.Context
5 |
6 | fun NotificationIds.post(context: Context, tag: String? = null, build: Notification.Builder.() -> Unit) =
7 | post(context, tag, @Suppress("DEPRECATION") Notification.Builder(context).also(build))
8 |
9 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/provisioning/CriticalAppsManager.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.provisioning;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.IBinder;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import com.oasisfeng.island.analytics.Analytics;
11 | import com.oasisfeng.island.engine.common.WellKnownPackages;
12 | import com.oasisfeng.island.util.Hacks;
13 | import com.oasisfeng.island.util.ProfileUser;
14 |
15 | import java.util.Set;
16 |
17 | import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
18 |
19 | /**
20 | * Detect critical apps (including system and installed ones).
21 | *
22 | * Created by Oasis on 2017/9/12.
23 | */
24 | public class CriticalAppsManager {
25 |
26 | @ProfileUser static Set<String> detectCriticalPackages(final PackageManager pm) {
27 | final Set<String> pkgs = SystemAppsManager.detectCriticalSystemPackages(pm);
28 |
29 | final String webview_pkg = getCurrentWebViewPackageName();
30 | if (webview_pkg != null) pkgs.add(webview_pkg);
31 | try { @SuppressLint("WrongConstant") // Chrome may not be current provider, since current provider may fallback to system WebView during provisioning.
32 | final ApplicationInfo chrome_info = pm.getApplicationInfo(WellKnownPackages.PACKAGE_GOOGLE_CHROME, MATCH_UNINSTALLED_PACKAGES);
33 | pkgs.add(chrome_info.packageName);
34 | } catch (final PackageManager.NameNotFoundException ignored) {}
35 | return pkgs;
36 | }
37 |
38 | public static @Nullable String getCurrentWebViewPackageName() {
39 | if (Hacks.ServiceManager_getService == null || Hacks.IWebViewUpdateService$Stub_asInterface == null
40 | || Hacks.IWebViewUpdateService_getCurrentWebViewPackageName == null) return null;
41 | try {
42 | final IBinder service = Hacks.ServiceManager_getService.invoke("webviewupdate").statically();
43 | if (service == null) throw new RuntimeException("Service not found: webviewupdate");
44 | final Object webview_service = Hacks.IWebViewUpdateService$Stub_asInterface.invoke(service).statically();
45 | return Hacks.IWebViewUpdateService_getCurrentWebViewPackageName.invoke().on(webview_service);
46 | } catch (final Exception e) {
47 | Analytics.$().logAndReport(TAG, "Error detecting WebView provider.", e);
48 | return null;
49 | }
50 | }
51 |
52 | private static final String TAG = "CriticalApps";
53 | }
54 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/settings/IslandSettings.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.settings
2 |
3 | import android.content.Context
4 | import android.database.ContentObserver
5 | import androidx.annotation.StringRes
6 | import com.oasisfeng.island.shared.R
7 | import com.oasisfeng.settings.AppSettings
8 |
9 | class IslandSettings(context: Context) {
10 |
11 | inner class DynamicShortcutLabel: BooleanSetting(R.string.setting_dynamic_shortcut_label)
12 |
13 | open inner class BooleanSetting(@StringRes prefKeyStringRes: Int, singleUser: Boolean = true)
14 | : IslandSetting<Boolean>(prefKeyStringRes, singleUser) {
15 | val enabled get() = mAppSettings.getBoolean(this)
16 | fun set(value: Boolean) = mAppSettings.set(this, value)
17 | }
18 |
19 | open inner class IslandSetting<T>(override val prefKeyResId: Int, override val isSingleUser: Boolean) : AppSettings.AppSetting<T> {
20 | /** Use [android.content.ContentResolver.unregisterContentObserver] to unregister. */
21 | fun registerObserver(observer: ContentObserver) = mAppSettings.registerObserver(this, observer)
22 | }
23 |
24 | val singleUserRootUri; get() = mAppSettings.singleUserRootUri
25 |
26 | private val mAppSettings = AppSettings(context)
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shortcut/ShortcutIcons.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shortcut;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.drawable.AdaptiveIconDrawable;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.graphics.drawable.Icon;
12 |
13 | import com.oasisfeng.android.ui.IconResizer;
14 | import com.oasisfeng.island.analytics.Analytics;
15 |
16 | import java.util.Objects;
17 |
18 | import androidx.annotation.Nullable;
19 | import androidx.annotation.RequiresApi;
20 |
21 | import static android.os.Build.VERSION_CODES.O;
22 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_CATEGORY;
23 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_ID;
24 |
25 | /**
26 | * Helper for shortcut icon.
27 | *
28 | * Created by Oasis on 2017/9/18.
29 | */
30 | public class ShortcutIcons {
31 |
32 | public static @Nullable Bitmap createLargeIconBitmap(final Context context, final Drawable drawable, final String pkg) {
33 | final int icon_size = Objects.requireNonNull((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
34 | final Drawable icon = new IconResizer(icon_size).createIconThumbnail(drawable); // Resize the app icon in case it's too large. (also avoid TransactionTooLargeException)
35 | icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
36 | final Bitmap bitmap = drawableToBitmap(icon);
37 | if (bitmap == null) Analytics.$().event("invalid_app_icon").with(ITEM_ID, pkg).with(ITEM_CATEGORY, drawable.getClass().getName()).send();
38 | return bitmap;
39 | }
40 |
41 | @RequiresApi(O) static Icon createAdaptiveIcon(final AdaptiveIconDrawable drawable) {
42 | final int width = drawable.getIntrinsicWidth() * 3 / 2, height = drawable.getIntrinsicHeight() * 3 / 2,
43 | start = drawable.getIntrinsicWidth() / 4, top = drawable.getIntrinsicHeight() / 4;
44 | drawable.setBounds(start, top, width - start, height - top);
45 | final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
46 | final Canvas canvas = new Canvas(bitmap);
47 | drawable.draw(canvas);
48 | return Icon.createWithAdaptiveBitmap(bitmap);
49 | }
50 |
51 | private static Bitmap drawableToBitmap(final Drawable drawable) {
52 | if (drawable instanceof BitmapDrawable)
53 | return ((BitmapDrawable) drawable).getBitmap();
54 | if (drawable instanceof ColorDrawable) {
55 | return null; //TODO: Support color drawable
56 | }
57 | final Bitmap bitmap = Bitmap.createBitmap(drawable.getBounds().width(), drawable.getBounds().height(), Bitmap.Config.ARGB_8888);
58 | final Canvas canvas = new Canvas(bitmap);
59 | drawable.draw(canvas);
60 | return bitmap;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/ActivityShuttle.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.content.pm.ResolveInfo;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Shuttle for cross-profile activity behaviors.
13 | *
14 | * Created by Oasis on 2019-3-4.
15 | */
16 | public class ActivityShuttle {
17 |
18 | public static Intent forceNeverForwarding(final PackageManager pm, final Intent intent) {
19 | final List<ResolveInfo> candidates = pm.queryIntentActivities(intent, 0);
20 | if (candidates != null) for (final ResolveInfo candidate : candidates) {
21 | if ("android".equals(candidate.activityInfo.packageName)) continue;
22 | return intent.setComponent(new ComponentName(candidate.activityInfo.packageName, candidate.activityInfo.name));
23 | }
24 | return intent;
25 | }
26 |
27 | public static ComponentName getForwarder(final Context context) {
28 | return selectForwarder(context.getPackageManager().queryIntentActivities(new Intent(ServiceShuttle.ACTION_BIND_SERVICE), 0));
29 | }
30 |
31 | private static ComponentName selectForwarder(final List<ResolveInfo> candidates) {
32 | if (candidates != null) for (final ResolveInfo candidate : candidates) {
33 | if (candidate.activityInfo == null || ! "android".equals(candidate.activityInfo.packageName)) continue;
34 | return new ComponentName(candidate.activityInfo.packageName, candidate.activityInfo.name);
35 | }
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/Closure.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle
2 |
3 | import android.content.Context
4 | import android.os.Parcel
5 | import android.os.Parcelable
6 | import java.lang.reflect.Modifier
7 |
8 | private typealias CtxFun<R> = Context.() -> R
9 |
10 | internal class Closure(private val functionClass: Class<CtxFun<*>>, private val variables: Array<Any?>): Parcelable {
11 |
12 | fun invoke(context: Context): Any? {
13 | val constructor = functionClass.declaredConstructors[0].apply { isAccessible = true }
14 | val args: Array<Any?> = constructor.parameterTypes.map(::getDefaultValue).toTypedArray()
15 | val variables = variables
16 | @Suppress("UNCHECKED_CAST") val block = constructor.newInstance(* args) as CtxFun<*>
17 | block.javaClass.getMemberFields().forEachIndexed { index, field -> // Constructor arguments do not matter, as all fields are replaced.
18 | field.set(block, when (field.type) {
19 | Context::class.java -> context
20 | Closure::class.java -> (variables[index] as? Closure)?.invoke(context)
21 | else -> variables[index] }) }
22 | return block(context)
23 | }
24 |
25 | constructor(procedure: CtxFun<*>): this(procedure.javaClass, extractVariablesFromFields(procedure)) {
26 | val constructors = javaClass.declaredConstructors
27 | require(constructors.isNotEmpty()) { "The method must have at least one constructor" }
28 | }
29 |
30 | private fun getDefaultValue(type: Class<*>)
31 | = if (type.isPrimitive) java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(type, 1), 0) else null
32 |
33 | override fun toString() = "Closure{${functionClass.name}}"
34 |
35 | override fun describeContents() = 0
36 | override fun writeToParcel(dest: Parcel, flags: Int) =
37 | dest.run { writeString(functionClass.name); writeArray(variables) }
38 | @Suppress("UNCHECKED_CAST") constructor(parcel: Parcel, cl: ClassLoader)
39 | : this(cl.loadClass(parcel.readString()) as Class<CtxFun<*>>, parcel.readArray(cl)!!)
40 |
41 | companion object CREATOR : Parcelable.ClassLoaderCreator<Closure> {
42 |
43 | override fun createFromParcel(parcel: Parcel, classLoader: ClassLoader) = Closure(parcel, classLoader)
44 | override fun createFromParcel(parcel: Parcel) = Closure(parcel, Closure::class.java.classLoader!!)
45 | override fun newArray(size: Int): Array<Closure?> = arrayOfNulls(size)
46 |
47 | // Automatically generated fields for captured variables, by compiler (indeterminate order)
48 | private fun extractVariablesFromFields(procedure: CtxFun<*>)
49 | = procedure.javaClass.getMemberFields().map { wrapIfNeeded(it.get(procedure)) }.toTypedArray()
50 |
51 | private fun <T> Class<T>.getMemberFields()
52 | = declaredFields.filter { if (Modifier.isStatic(it.modifiers)) false else { it.isAccessible = true; true }}
53 |
54 | private fun wrapIfNeeded(obj: Any?): Any? = if (obj is Context) null else obj
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/ContextShuttle.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.content.pm.PackageManager.NameNotFoundException;
6 | import android.os.UserHandle;
7 |
8 | import androidx.annotation.Nullable;
9 | import androidx.annotation.RequiresPermission;
10 |
11 | import com.oasisfeng.android.content.pm.Permissions;
12 | import com.oasisfeng.island.util.Hacks;
13 |
14 | /**
15 | * Utility class for cross-user context related stuffs.
16 | *
17 | * Created by Oasis on 2017/9/1.
18 | */
19 | public class ContextShuttle {
20 |
21 | @RequiresPermission(Permissions.INTERACT_ACROSS_USERS)
22 | public static @Nullable PackageManager getPackageManagerAsUser(final Context context, final UserHandle user) {
23 | try {
24 | final Context user_context = createContextAsUser(context, user);
25 | return user_context != null ? user_context.getPackageManager() : null;
26 | } catch (final NameNotFoundException ignored) { return null; } // Should never happen
27 | }
28 |
29 | @RequiresPermission(Permissions.INTERACT_ACROSS_USERS)
30 | public static @Nullable Context createPackageContextAsUser(final Context context, final String pkg, final UserHandle user) throws NameNotFoundException {
31 | return Hacks.Context_createPackageContextAsUser.invoke(pkg, 0, user).on(context);
32 | }
33 |
34 | public static @Nullable Context createContextAsUser(final Context context, final UserHandle user) throws NameNotFoundException {
35 | return createPackageContextAsUser(context, "system", user);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/MethodInvocation.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * The detail of method invocation to be shuttled.
8 | *
9 | * Created by Oasis on 2017/4/2.
10 | */
11 | class MethodInvocation<Result> implements Parcelable {
12 |
13 | String clazz;
14 | Object[] args;
15 | Result result;
16 | Throwable throwable;
17 |
18 | MethodInvocation() {}
19 |
20 | private MethodInvocation(final Parcel in) {
21 | clazz = in.readString();
22 | args = in.readArray(getClass().getClassLoader());
23 | }
24 |
25 | void readFromParcel(final Parcel parcel) { //noinspection unchecked
26 | result = (Result) parcel.readValue(getClass().getClassLoader());
27 | throwable = (Throwable) parcel.readSerializable();
28 | }
29 |
30 | @Override public void writeToParcel(final Parcel dest, final int flags) {
31 | if ((flags & PARCELABLE_WRITE_RETURN_VALUE) == 0) {
32 | dest.writeString(clazz);
33 | dest.writeArray(args);
34 | } else {
35 | dest.writeValue(result);
36 | dest.writeSerializable(throwable);
37 | }
38 | }
39 |
40 | @Override public int describeContents() { return 0; }
41 |
42 | static final Creator<MethodInvocation> CREATOR = new Creator<MethodInvocation>() {
43 | @Override public MethodInvocation createFromParcel(final Parcel in) { return new MethodInvocation(in); }
44 | @Override public MethodInvocation[] newArray(final int size) { return new MethodInvocation[size]; }
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/Shuttle.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle
2 |
3 | import android.content.Context
4 | import android.os.UserHandle
5 | import com.oasisfeng.island.util.Users
6 |
7 | class Shuttle(val context: Context, val to: UserHandle) {
8 |
9 | /** @return Job if launched in coroutine, otherwise null. */
10 | fun launch(function: Context.() -> Unit) = if (to == Users.current()) { function(context) } else shuttle(function)
11 |
12 | fun <R> invoke(function: Context.() -> R)
13 | = if (to == Users.current()) context.function() else shuttle(function)
14 |
15 | /* Helpers to avoid redundant local variables. ("inline" is used to ensure only "Context.() -> R" function is shuttled) */
16 | inline fun <A> launch(with: A, crossinline function: Context.(A) -> Unit) { launch { function(with) }}
17 | inline fun <A, R> invoke(with: A, crossinline function: Context.(A) -> R)
18 | = invoke { this.function(with) }
19 |
20 | private fun <R> shuttle(function: Context.() -> R): R {
21 | val result = ShuttleProvider.call(context, to, function)
22 | if (result.isNotReady()) throw IllegalStateException("Shuttle not ready")
23 | return result.get()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/CallerAwareActivity.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.IBinder;
7 | import android.util.Log;
8 |
9 | import androidx.annotation.Nullable;
10 |
11 | /**
12 | * Activity with enhanced {@link #getCallingPackage()}, which detects caller even if not started by {@link #startActivityForResult(Intent, int)}.
13 | *
14 | * Created by Oasis on 2019-2-27.
15 | */
16 | public abstract class CallerAwareActivity extends Activity {
17 |
18 | @Override public @Nullable String getCallingPackage() {
19 | final String caller = super.getCallingPackage();
20 | if (caller != null) return caller;
21 | return getCallingPackage(this);
22 | }
23 |
24 | public static @Nullable String getCallingPackage(final Activity activity) {
25 | Intent original_intent = null;
26 | final Intent intent = activity.getIntent();
27 | if (intent.hasExtra(Intent.EXTRA_REFERRER) || intent.hasExtra(Intent.EXTRA_REFERRER_NAME)) {
28 | original_intent = new Intent(activity.getIntent());
29 | intent.removeExtra(Intent.EXTRA_REFERRER);
30 | intent.removeExtra(Intent.EXTRA_REFERRER_NAME);
31 | }
32 | try {
33 | final Uri referrer = activity.getReferrer(); // getReferrer() returns real calling package if no referrer extras
34 | if (referrer != null) return referrer.getAuthority(); // Referrer URI: android-app://<package name>
35 | } finally {
36 | if (original_intent != null) activity.setIntent(original_intent);
37 | }
38 | if (Hacks.IActivityManager_getLaunchedFromPackage != null) try {
39 | final Object am = Hacks.ActivityManagerNative_getDefault.invoke().statically();
40 | if (am != null) {
41 | final IBinder token = Hacks.Activity_getActivityToken.invoke().on(activity);
42 | return token != null ? Hacks.IActivityManager_getLaunchedFromPackage.invoke(token).on(am) : null;
43 | }
44 | } catch (final Exception e) {
45 | Log.e(TAG, "Error detecting caller", e);
46 | }
47 | return null;
48 | }
49 |
50 | private static final String TAG = "Island.CAA";
51 | }
52 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/DeviceAdmins.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.app.admin.DeviceAdminReceiver;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.content.pm.ResolveInfo;
10 |
11 | import com.oasisfeng.island.analytics.Analytics;
12 | import com.oasisfeng.island.shared.BuildConfig;
13 |
14 | import java.util.List;
15 |
16 | import static android.content.Context.DEVICE_POLICY_SERVICE;
17 | import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
18 | import static android.content.pm.PackageManager.DONT_KILL_APP;
19 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_ID;
20 | import static java.util.Objects.requireNonNull;
21 |
22 | /**
23 | * Utility class for device-admin related functions
24 | *
25 | * Created by Oasis on 2017/2/19.
26 | */
27 | public class DeviceAdmins {
28 |
29 | public static ComponentName getComponentName(final Context context) {
30 | if (sDeviceAdminComponent == null) sDeviceAdminComponent = queryComponentName(context);
31 | return sDeviceAdminComponent;
32 | }
33 |
34 | private static ComponentName queryComponentName(final Context context) {
35 | final List<ComponentName> active_admins = requireNonNull((DevicePolicyManager) context.getSystemService(DEVICE_POLICY_SERVICE)).getActiveAdmins();
36 | if (active_admins != null && ! active_admins.isEmpty()) for (final ComponentName active_admin : active_admins)
37 | if (Modules.MODULE_ENGINE.equals(active_admin.getPackageName())) return active_admin;
38 |
39 | final Intent intent = new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED).setPackage(Modules.MODULE_ENGINE);
40 | final List<ResolveInfo> admins = context.getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_DISABLED_COMPONENTS);
41 | if (admins.size() == 1) {
42 | final ResolveInfo admin = admins.get(0);
43 | sDeviceAdminComponent = new ComponentName(Modules.MODULE_ENGINE, admins.get(0).activityInfo.name);
44 | if (! admin.activityInfo.enabled) {
45 | Analytics.$().event("device_admin_component_disabled").with(ITEM_ID, sDeviceAdminComponent.flattenToShortString()).send();
46 | context.getPackageManager().setComponentEnabledSetting(sDeviceAdminComponent, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
47 | }
48 | return sDeviceAdminComponent;
49 | } // No resolve result on some Android 7.x devices, cause unknown.
50 | if (BuildConfig.DEBUG) throw new IllegalStateException("Engine module is not correctly installed: " + admins);
51 | return new ComponentName(Modules.MODULE_ENGINE, "com.oasisfeng.island.IslandDeviceAdminReceiver"); // Fallback
52 | }
53 |
54 | private static ComponentName sDeviceAdminComponent;
55 | }
56 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/DevicePolicies.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | import android.app.admin.DevicePolicyManager
4 |
5 | typealias DPM = DevicePolicyManager
6 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/OwnerUser.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.CONSTRUCTOR;
7 | import static java.lang.annotation.ElementType.FIELD;
8 | import static java.lang.annotation.ElementType.METHOD;
9 | import static java.lang.annotation.ElementType.TYPE;
10 | import static java.lang.annotation.RetentionPolicy.SOURCE;
11 |
12 | /**
13 | * Annotation to indicate running in owner user.
14 | *
15 | * Created by Oasis on 2016/11/27.
16 | */
17 | @Retention(SOURCE)
18 | @Target({TYPE,METHOD,CONSTRUCTOR,FIELD})
19 | public @interface OwnerUser {}
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/Permissions.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.os.Process;
6 |
7 | import javax.annotation.ParametersAreNonnullByDefault;
8 |
9 | /**
10 | * Permission-related helpers
11 | *
12 | * Created by Oasis on 2017/10/8.
13 | */
14 | @ParametersAreNonnullByDefault
15 | public class Permissions {
16 |
17 | public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
18 |
19 | private static final int PID = Process.myPid();
20 | private static final int UID = Process.myUid();
21 |
22 | public static boolean has(final Context context, final String permission) {
23 | return context.checkPermission(permission, PID, UID) == PackageManager.PERMISSION_GRANTED;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/ProfileUser.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.CONSTRUCTOR;
7 | import static java.lang.annotation.ElementType.FIELD;
8 | import static java.lang.annotation.ElementType.METHOD;
9 | import static java.lang.annotation.ElementType.TYPE;
10 | import static java.lang.annotation.RetentionPolicy.SOURCE;
11 |
12 | /**
13 | * Annotation to indicate running in profile user.
14 | *
15 | * Created by Oasis on 2016/11/27.
16 | */
17 | @Retention(SOURCE)
18 | @Target({TYPE,METHOD,CONSTRUCTOR,FIELD})
19 | public @interface ProfileUser {}
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/RomVariants.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | object RomVariants {
4 |
5 | @JvmStatic fun isMiui() = ! Hacks.SystemProperties_get.invoke("ro.miui.ui.version.name").statically().isNullOrBlank()
6 | }
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/UserHandles.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | import android.os.UserHandle
4 |
5 | fun UserHandle.toId() = hashCode()
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/perf/Performances.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.perf;
2 |
3 | import android.os.SystemClock;
4 | import android.util.Log;
5 |
6 | import com.oasisfeng.deagle.BuildConfig;
7 |
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import androidx.annotation.Nullable;
11 |
12 | /**
13 | * Utility class for performance related stuffs
14 | *
15 | * Created by Oasis on 2016/9/25.
16 | */
17 | public class Performances {
18 |
19 | private static final boolean DEBUG = BuildConfig.DEBUG;
20 |
21 | public static final class Debug {
22 |
23 | public static @Nullable Stopwatch startUptimeStopwatch() {
24 | return DEBUG ? Stopwatch.createStarted(TICKER_UPTIME) : null;
25 | }
26 |
27 | public static @Nullable Stopwatch startThreadCpuTimeStopwatch() {
28 | return DEBUG ? Stopwatch.createStarted(TICKER_THREAD_CPU_TIME) : null;
29 | }
30 | }
31 |
32 | public static final Ticker TICKER_UPTIME = new Ticker() { @Override public long read() { return SystemClock.uptimeMillis() * 1_000_000L; }};
33 | public static final Ticker TICKER_THREAD_CPU_TIME = new Ticker() { @Override public long read() { return android.os.Debug.threadCpuTimeNanos(); }};
34 |
35 | public static @Nullable Stopwatch startUptimeStopwatch() {
36 | return Stopwatch.createStarted(TICKER_UPTIME);
37 | }
38 |
39 | public static @Nullable Stopwatch startThreadCpuTimeStopwatch() {
40 | return Stopwatch.createStarted(TICKER_THREAD_CPU_TIME);
41 | }
42 |
43 | public static void check(final @Nullable Stopwatch stopwatch, final long threshold_millis, final String what) {
44 | if (stopwatch == null) return;
45 | final long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
46 | if (duration > threshold_millis) Log.w(TAG, duration + " ms spent in " + what);
47 | }
48 |
49 | private static final String TAG = "Performance";
50 | }
51 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/perf/Ticker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | package com.oasisfeng.perf;
16 |
17 | /**
18 | * A time source; returns a time value representing the number of nanoseconds elapsed since some
19 | * fixed but arbitrary point in time. Note that most users should use {@link Stopwatch} instead of
20 | * interacting with this class directly.
21 | *
22 | * <p><b>Warning:</b> this interface can only be used to measure elapsed time, not wall time.
23 | *
24 | * @author Kevin Bourrillion
25 | * @since 10.0 (<a href="https://github.com/google/guava/wiki/Compatibility">mostly
26 | * source-compatible</a> since 9.0)
27 | */
28 | public abstract class Ticker {
29 | /**
30 | * Constructor for use by subclasses.
31 | */
32 | protected Ticker() {}
33 |
34 | /**
35 | * Returns the number of nanoseconds elapsed since this ticker's fixed point of reference.
36 | */
37 | public abstract long read();
38 |
39 | /**
40 | * A ticker that reads the current time using {@link System#nanoTime}.
41 | *
42 | * @since 10.0
43 | */
44 | public static Ticker systemTicker() {
45 | return SYSTEM_TICKER;
46 | }
47 |
48 | private static final Ticker SYSTEM_TICKER = new Ticker() { @Override public long read() { return System.nanoTime(); }};
49 | }
50 |
--------------------------------------------------------------------------------
/shared/src/main/res/drawable/ic_landscape_black_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="24dp"
4 | android:viewportWidth="24.0"
5 | android:viewportHeight="24.0">
6 | <path
7 | android:fillColor="@color/action_icon"
8 | android:pathData="M14,6l-3.75,5 2.85,3.8 -1.6,1.2C9.81,13.75 7,10 7,10l-6,8h22L14,6z"/>
9 | </vector>
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/drawable/ic_settings_applications_white_24dp.xml:
--------------------------------------------------------------------------------
1 | <vector android:height="24dp" android:tint="#FFFFFF"
2 | android:viewportHeight="24.0" android:viewportWidth="24.0"
3 | android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 | <path android:fillColor="#FF000000" android:pathData="M12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM17.25,12c0,0.23 -0.02,0.46 -0.05,0.68l1.48,1.16c0.13,0.11 0.17,0.3 0.08,0.45l-1.4,2.42c-0.09,0.15 -0.27,0.21 -0.43,0.15l-1.74,-0.7c-0.36,0.28 -0.76,0.51 -1.18,0.69l-0.26,1.85c-0.03,0.17 -0.18,0.3 -0.35,0.3h-2.8c-0.17,0 -0.32,-0.13 -0.35,-0.29l-0.26,-1.85c-0.43,-0.18 -0.82,-0.41 -1.18,-0.69l-1.74,0.7c-0.16,0.06 -0.34,0 -0.43,-0.15l-1.4,-2.42c-0.09,-0.15 -0.05,-0.34 0.08,-0.45l1.48,-1.16c-0.03,-0.23 -0.05,-0.46 -0.05,-0.69 0,-0.23 0.02,-0.46 0.05,-0.68l-1.48,-1.16c-0.13,-0.11 -0.17,-0.3 -0.08,-0.45l1.4,-2.42c0.09,-0.15 0.27,-0.21 0.43,-0.15l1.74,0.7c0.36,-0.28 0.76,-0.51 1.18,-0.69l0.26,-1.85c0.03,-0.17 0.18,-0.3 0.35,-0.3h2.8c0.17,0 0.32,0.13 0.35,0.29l0.26,1.85c0.43,0.18 0.82,0.41 1.18,0.69l1.74,-0.7c0.16,-0.06 0.34,0 0.43,0.15l1.4,2.42c0.09,0.15 0.05,0.34 -0.08,0.45l-1.48,1.16c0.03,0.23 0.05,0.46 0.05,0.69z"/>
5 | </vector>
6 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Global style colors -->
4 | <color name="primary">#303F9F</color>
5 | <color name="primary_dark">#1A237E</color>
6 | <color name="accent">#c67100</color>
7 |
8 | <color name="action_icon">@android:color/white</color>
9 | </resources>
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_channel_ongoing_task">Tarefa em andamento</string>
4 | <string name="notification_channel_important">Importante</string>
5 | <string name="notification_channel_app_install">Instalação do app</string>
6 | <string name="notification_channel_island_watcher">Observador do Island</string>
7 | <string name="notification_channel_app_watcher">Observador do app</string>
8 | <string name="notification_channel_debug">Depurar</string>
9 |
10 | <string name="device_admin_support_message_short">Para desativar esta restrição, veja a configuração relacionada do Island.</string>
11 | <string name="device_admin_support_message_long">Quando o Island é ativado, você pode ver \"Dispositivo é gerenciado por uma organização\" ou informação similar no IU do sistema. É um aviso padrão de transparência do Perfil de Trabalho (o recurso subjacente de Android usado pelo Island), que não pode ser removido.\n\nO Island não coleta suas informações pessoais de forma nenhuma, consulte nossa política de privacidade na Loja do Google Play para obter mais detalhes.\n\nSe receber uma mensagem \"xxx recurso desativado por admin\", neste caso você podia ter ativado um dos recursos de restrição no Island, por favor, consulte a configuração relacionada no Island.\n\nPara desinstalar o Island, você deve desativá-lo primeiro, vá para Island - Configurações - Configurações Avançadas - Destruir / Desativar.</string>
12 |
13 | <string name="prompt_failed_preserving_app_ops">App Ops não foram preservados devido a um erro interno.</string>
14 | <string name="notification_profile_shuttle_pending_title">A funcionalidade de configurações intercambiáveis é limitada</string>
15 | <string name="notification_profile_shuttle_pending_text">Toque para restaurar</string>
16 | </resources>
17 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v24/flags.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="until_api_24">false</bool>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v26/flags.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="since_api_26">true</bool>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v28/flags.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="since_api_28">true</bool>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_channel_ongoing_task">執行中的任務</string>
4 | <string name="notification_channel_important">重要事項</string>
5 | <string name="notification_channel_app_install">應用程式安裝</string>
6 | <string name="notification_channel_island_watcher">諸界 守望者</string>
7 | <string name="notification_channel_app_watcher">應用程式 守望者</string>
8 | <string name="notification_channel_debug">偵錯</string>
9 |
10 | <string name="device_admin_support_message_short">如需取消此限制,請至“煉妖壺”中調整對應的設定。\n\n應用程式權限:限制修改狀態表明此權限已被撤銷。如需恢復,請至“設定 - 分空間的獨立設定 - 隱私保護”。</string>
11 | <string name="device_admin_support_message_long">您可能注意到系统界面中出现的“裝置由您所在的單位負責管理”或類似訊息,這是 Android 工作資料(壺中界的 Android 底層機制)啟用後的透明性告知,無法隱藏。\n\n我們不會以任何方式收集您的個人隐私訊息,請參考在 Google Play 商店中發布的隱私政策。\n\n如果遇到“XX 特性已被管理員停用”,可能是由於您打開了某項限制功能,請至此應用程式中調整對應的設定。</string>
12 |
13 | <string name="prompt_failed_preserving_app_ops">App Ops 未能保留(內部異常)</string>
14 | </resources>
--------------------------------------------------------------------------------
/shared/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_channel_ongoing_task">执行中的任务</string>
4 | <string name="notification_channel_important">重要事项</string>
5 | <string name="notification_channel_app_install">应用安装</string>
6 | <string name="notification_channel_island_watcher">诸界 守望者</string>
7 | <string name="notification_channel_app_watcher">应用 守望者</string>
8 | <string name="notification_channel_debug">调试</string>
9 |
10 | <string name="device_admin_support_message_short">如需取消此限制,请至“炼妖壶”中调整相应的设置。\n\n应用权限:限制修改状态表明此权限已被撤销。如需恢复,请至“设置 - 分空间的独立设置 - 隐私保护”。</string>
11 | <string name="device_admin_support_message_long">您可能注意到系统界面中出现的“设备由您所在的单位负责管理”或类似信息,这是 Android 工作资料(壶中界的 Android 底层机制)激活后的透明性告知,无法隐去。\n\n我们不会以任何方式收集您的个人隐私信息,请参阅在 Google Play 市场中公示的隐私政策。\n\n如果遇到“XX 特性已被管理员禁用”,可能是由于您打开了某项限制功能,请至此应用中调整相应的设置。</string>
12 |
13 | <string name="prompt_failed_preserving_app_ops">App Ops 未能保留(内部异常)</string>
14 |
15 | <string name="notification_profile_shuttle_pending_title">跨界功能受限</string>
16 | <string name="notification_profile_shuttle_pending_text">点此恢复</string>
17 | </resources>
--------------------------------------------------------------------------------
/shared/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Global style colors -->
4 | <color name="primary">#3F51B5</color>
5 | <color name="primary_dark">#303F9F</color>
6 | <color name="accent">#FFA000</color>
7 |
8 | <color name="action_icon">@android:color/black</color>
9 | </resources>
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/flags.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="until_api_24">true</bool>
4 | <bool name="since_api_26">false</bool>
5 | <bool name="since_api_28">false</bool>
6 | </resources>
7 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/settings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string translatable="false" name="setting_dynamic_shortcut_label">dynamic_shortcut_label</string>
4 | </resources>
--------------------------------------------------------------------------------
/shared/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <string name="notification_channel_ongoing_task">Ongoing task</string>
4 | <string name="notification_channel_important">Important</string>
5 | <string name="notification_channel_app_install">App Installation</string>
6 | <string name="notification_channel_island_watcher">Island Watcher</string>
7 | <string name="notification_channel_app_watcher">App Watcher</string>
8 | <string name="notification_channel_debug">Debug</string>
9 |
10 | <string name="device_admin_support_message_short">To disable this restriction, please refer to corresponding setting in Island.</string>
11 | <string name="device_admin_support_message_long">When Island is activated, you may see \"device is managed by your organization\" or similar information around the system UI. It\'s the standard transparency notice of Android for Work (the underlying Android feature used by Island), which cannot be removed.\n\nIsland does not collect your personal information in any means, see our privacy policy on Google Play Store for details.\n\nIf you get message \"xxx feature disabled by admin\", you may have enabled one of the restriction features in Island, please refer to corresponding setting in Island.\n\nTo uninstall Island, you must deactivate it first, go to Island - Settings - Scoped Settings - Destroy / Deactivate.</string>
12 |
13 | <string name="prompt_failed_preserving_app_ops">App Ops was not preserved due to internal error.</string>
14 |
15 | <string name="notification_profile_shuttle_pending_title">Cross-land functionality is limited</string>
16 | <string name="notification_profile_shuttle_pending_text">Tap to restore</string>
17 | </resources>
--------------------------------------------------------------------------------
/shared/src/main/res/xml/analytics_tracker.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8" ?>
2 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="UnusedResources">
3 | <string name="ga_trackingId">UA-77327039-1</string>
4 | <integer name="ga_sessionTimeout">180</integer>
5 | <bool name="ga_autoActivityTracking">false</bool>
6 | <bool name="ga_reportUncaughtExceptions">false</bool>
7 | </resources>
--------------------------------------------------------------------------------
/shared/src/main/res/xml/config_defaults.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <defaultsMap>
3 | <entry>
4 | <key>url_faq</key>
5 | <value>https://island.oasisfeng.com/faq</value>
6 | </entry>
7 | <entry>
8 | <key>url_setup</key>
9 | <value>https://island.oasisfeng.com/setup</value>
10 | </entry>
11 | <entry>
12 | <key>url_setup_god_mode</key>
13 | <value>https://island.oasisfeng.com/setup#manual-setup-for-island-in-god-mode</value>
14 | </entry>
15 | <entry>
16 | <key>url_setup_trouble</key>
17 | <value>https://island.oasisfeng.com/faq</value>
18 | </entry>
19 | <entry>
20 | <key>url_file_shuttle</key>
21 | <value>https://island.oasisfeng.com/files</value>
22 | </entry>
23 | <entry>
24 | <key>permission_allowed_apps</key>
25 | <value>com.oasisfeng.greenify,com.oasisfeng.nevo</value>
26 | </entry>
27 | </defaultsMap>
28 |
--------------------------------------------------------------------------------
/watcher/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion this.compileSdkVersion
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
10 | }
11 |
12 | kotlinOptions.jvmTarget = "1.8"
13 |
14 | compileOptions {
15 | sourceCompatibility JavaVersion.VERSION_1_8
16 | targetCompatibility JavaVersion.VERSION_1_8
17 | }
18 | }
19 |
20 | dependencies {
21 | implementation project(':shared')
22 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
23 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
24 |
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'androidx.test:runner:1.3.0'
27 | }
28 |
--------------------------------------------------------------------------------
/watcher/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <manifest package="com.oasisfeng.island.watcher" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
2 |
3 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- IslandWatcher -->
4 |
5 | <application tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
6 |
7 | <receiver android:name=".IslandWatcher" android:enabled="@bool/since_api_28" android:directBootAware="true">
8 | <intent-filter>
9 | <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
10 | <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
11 | <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
12 | <action android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
13 | </intent-filter>
14 | </receiver>
15 | <service android:name=".IslandWatcher$IslandDeactivationService" android:directBootAware="true" />
16 |
17 | <receiver android:name=".IslandAppWatcher" android:enabled="@bool/since_api_26" android:directBootAware="true">
18 | <intent-filter>
19 | <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
20 | <data android:scheme="package" />
21 | </intent-filter>
22 | </receiver>
23 | <provider android:name=".IslandAppWatcher$AppStateTracker"
24 | android:authorities="com.oasisfeng.island.watcher"
25 | android:exported="false"
26 | android:enabled="@bool/since_api_26"
27 | android:directBootAware="true" />
28 |
29 | <activity android:name=".IslandWatcher$DummyHomeActivity"
30 | android:theme="@android:style/Theme.NoDisplay"
31 | android:hardwareAccelerated="false"
32 | android:enabled="false"
33 | android:directBootAware="true">
34 | <intent-filter>
35 | <action android:name="android.intent.action.MAIN" />
36 | <category android:name="android.intent.category.HOME" />
37 | <category android:name="android.intent.category.DEFAULT" />
38 | </intent-filter>
39 | </activity>
40 |
41 | </application>
42 | </manifest>
43 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="notification_island_watcher_title">O serviço do Island está ativo</string>
3 | <string name="notification_island_watcher_text">Desativar a fim de parar todas as atividades internas em segundo plano.</string>
4 | <string name="notification_app_watcher_title">%s está ativo</string>
5 | <string name="notification_app_watcher_text">Clique para suspender de novo</string>
6 | <string name="action_deactivate_island">Desativar</string>
7 | <string name="action_restart_island">Recomeçar</string>
8 | <string name="action_settings">Configurações</string>
9 | <string name="action_dismiss">Descartar</string>
10 |
11 | <string name="toast_manual_quiet_mode">Desativação falhou, por favor faça-o manualmente.</string>
12 |
13 | <string name="notification_permission_was_granted_title">Permissão \"%s\" foi concedida</string>
14 | <string name="notification_permission_was_granted_text">Gostaria de revogar esta permissão?</string>
15 | <string name="action_revoke_granted">Revogar</string>
16 | <string name="action_keep_granted">Manter concedida</string>
17 | </resources>
18 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="notification_island_watcher_title">壺中界 運轉中</string>
3 | <string name="notification_island_watcher_text">封印它可終止其中所有的背景活動</string>
4 | <string name="notification_app_watcher_title">%s 已解凍</string>
5 | <string name="notification_app_watcher_text">點擊將其解凍</string>
6 | <string name="action_deactivate_island">封印</string>
7 | <string name="action_settings">設定</string>
8 | <string name="action_dismiss">忽略</string>
9 |
10 | <string name="toast_manual_quiet_mode">停止工作資料失敗,請手動關閉。</string>
11 |
12 | <string name="notification_permission_was_granted_title">已授權权限“%s”</string>
13 | <string name="notification_permission_was_granted_text">是否撤銷此權限?</string>
14 | <string name="action_revoke_granted">撤銷</string>
15 | <string name="action_keep_granted">保持授權</string>
16 | </resources>
17 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="notification_island_watcher_title">壶中界 运转中</string>
3 | <string name="notification_island_watcher_text">封印它可终止其中所有的潜在活动</string>
4 | <string name="notification_app_watcher_title">%s 已解冻</string>
5 | <string name="notification_app_watcher_text">点击将其冻结</string>
6 | <string name="action_deactivate_island">封印</string>
7 | <string name="action_restart_island">重启</string>
8 | <string name="action_settings">设置</string>
9 | <string name="action_dismiss">忽略</string>
10 |
11 | <string name="toast_manual_quiet_mode">停止工作资料失败,请手动关闭。</string>
12 |
13 | <string name="notification_permission_was_granted_title">已授予权限“%s”</string>
14 | <string name="notification_permission_was_granted_text">是否撤销此权限?</string>
15 | <string name="action_revoke_granted">撤销</string>
16 | <string name="action_keep_granted">保持授予</string>
17 | </resources>
18 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="notification_island_watcher_title">Island space is active</string>
3 | <string name="notification_island_watcher_text">Deactivate to cease all the background activities inside.</string>
4 | <string name="notification_app_watcher_title">%s is active</string>
5 | <string name="notification_app_watcher_text">Click to re-freeze</string>
6 | <string name="action_deactivate_island">Deactivate</string>
7 | <string name="action_restart_island">Restart</string>
8 | <string name="action_settings">Settings</string>
9 | <string name="action_dismiss">Dismiss</string>
10 |
11 | <string name="toast_manual_quiet_mode">Failed to deactivate, please toggle manually.</string>
12 |
13 | <string name="notification_permission_was_granted_title">Permission \"%s\" was granted</string>
14 | <string name="notification_permission_was_granted_text">Would you like to revoke this permission?</string>
15 | <string name="action_revoke_granted">Revoke</string>
16 | <string name="action_keep_granted">Keep granted</string>
17 | </resources>
18 |
--------------------------------------------------------------------------------