├── .gitignore ├── .idea ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── misc.xml ├── shelf │ ├── Uncommitted_changes_before_Checkout_at_2023_8_28_17_28_[Changes] │ │ └── shelved.patch │ ├── Uncommitted_changes_before_Checkout_at_2023_8_28_17_28_[Changes]1 │ │ └── shelved.patch │ └── Uncommitted_changes_before_Checkout_at_2023_8_28_17_28__Changes_.xml └── vcs.xml ├── LICENSE ├── README.md ├── README_zh-CN.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sunshine │ │ └── freeform │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── java │ │ └── com │ │ │ └── sunshine │ │ │ └── freeform │ │ │ ├── MiFreeform.kt │ │ │ ├── MiFreeformServiceManager.kt │ │ │ ├── hook │ │ │ └── utils │ │ │ │ └── XLog.kt │ │ │ ├── receiver │ │ │ ├── BootReceiver.kt │ │ │ └── StartFreeformReceiver.kt │ │ │ ├── room │ │ │ ├── DatabaseRepository.kt │ │ │ ├── FreeFormAppsDao.kt │ │ │ ├── FreeFormAppsEntity.kt │ │ │ └── MyDatabase.kt │ │ │ ├── service │ │ │ ├── FloatingService.kt │ │ │ ├── QuickStartTileService.kt │ │ │ ├── ServiceViewModel.kt │ │ │ └── SidebarService.kt │ │ │ ├── systemapi │ │ │ └── UserHandle.kt │ │ │ ├── ui │ │ │ ├── ProcessSurfaceView.kt │ │ │ ├── app_list │ │ │ │ ├── AppInfo.kt │ │ │ │ ├── AppListActivity.kt │ │ │ │ ├── FreeformAppActivity.kt │ │ │ │ └── SearchView.kt │ │ │ ├── floating │ │ │ │ ├── ChooseAppFloatingAdapter.kt │ │ │ │ ├── FloatingActivity.kt │ │ │ │ ├── FloatingViewModel.kt │ │ │ │ └── FloatingWindow.kt │ │ │ ├── freeform │ │ │ │ ├── FreeformActivity.kt │ │ │ │ └── FreeformViewModel.kt │ │ │ ├── main │ │ │ │ ├── HomeView.kt │ │ │ │ ├── LogView.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── RemoteSettings.kt │ │ │ │ └── SettingView.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── utils │ │ │ ├── Debug.kt │ │ │ ├── Extension.kt │ │ │ ├── Logger.kt │ │ │ ├── PackageUtils.kt │ │ │ └── ServiceUtils.kt │ └── res │ │ ├── anim │ │ ├── slide_in_from_left.xml │ │ ├── slide_in_from_right.xml │ │ ├── slide_out_to_left.xml │ │ └── slide_out_to_right.xml │ │ ├── drawable-night │ │ ├── color_background.xml │ │ ├── ic_add.xml │ │ └── ic_all.xml │ │ ├── drawable │ │ ├── bar_corners_bg.xml │ │ ├── color_background.xml │ │ ├── ic_add.xml │ │ ├── ic_all.xml │ │ ├── ic_home.xml │ │ ├── ic_log.xml │ │ ├── ic_save.xml │ │ ├── ic_setting.xml │ │ ├── ic_wrap_text.xml │ │ └── tile_icon.png │ │ ├── layout │ │ ├── activity_floating.xml │ │ ├── appbar.xml │ │ ├── appbar_fragment_activity.xml │ │ ├── item_floating.xml │ │ ├── view_choose_app.xml │ │ ├── view_floating.xml │ │ └── view_freeform.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── sunshine │ └── freeform │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── freeform-server ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── customize.d │ │ │ ├── 50-copy-mfdex-to-framework.sh │ │ │ └── 51-active-lsp.sh │ │ ├── post-fs-data.sh │ │ ├── sepolicy.rule │ │ ├── sqlite3 │ │ └── system │ │ │ ├── .placeholder │ │ │ ├── etc │ │ │ ├── .placeholder │ │ │ └── permissions │ │ │ │ └── privapp_allowlist_io_sunshine0523_sidebar.xml │ │ │ └── priv-app │ │ │ ├── .placeholder │ │ │ ├── Mi-Freeform-Sidebar │ │ │ └── app-release.apk │ │ │ └── Mi-Freeform │ │ │ └── app-release.apk │ └── java │ │ ├── com │ │ └── android │ │ │ └── server │ │ │ └── display │ │ │ ├── MiFreeformDisplayAdapter.java │ │ │ ├── MiFreeformRDisplayAdapter.java │ │ │ ├── MiFreeformTDisplayAdapter.java │ │ │ └── MiFreeformUDisplayAdapter.java │ │ └── io │ │ └── sunshine0523 │ │ └── freeform │ │ ├── ZygoteMain.java │ │ ├── notification │ │ └── FreeformNotificationListener.kt │ │ ├── service │ │ ├── MiFreeformService.java │ │ ├── MiFreeformServiceHolder.java │ │ ├── MiFreeformUIService.java │ │ ├── SideBarService.java │ │ └── SystemServiceHolder.java │ │ ├── ui │ │ ├── freeform │ │ │ ├── AppConfig.kt │ │ │ ├── FreeformAnimation.kt │ │ │ ├── FreeformConfig.kt │ │ │ ├── FreeformTaskStackListener.kt │ │ │ ├── FreeformTextureView.kt │ │ │ ├── FreeformWindow.kt │ │ │ ├── FreeformWindowManager.java │ │ │ ├── RemoteResourceHolder.kt │ │ │ ├── RotationWatcher.kt │ │ │ ├── TouchListener.kt │ │ │ └── UIConfig.kt │ │ └── sidebar │ │ │ ├── MGestureManager.kt │ │ │ ├── SideBarTouchListener.kt │ │ │ └── SideBarWindow.kt │ │ └── util │ │ ├── DataHelper.kt │ │ ├── MLog.java │ │ └── Settings.kt │ └── test │ └── java │ └── io │ └── sunshine0523 │ └── freeform │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hidden-api ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── sunshine0523 │ │ └── hidden │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ ├── android │ │ ├── annotation │ │ │ ├── NonNull.java │ │ │ └── Nullable.java │ │ ├── app │ │ │ ├── ActivityManagerHidden.java │ │ │ ├── ActivityOptionsHidden.java │ │ │ ├── ActivityThread.java │ │ │ ├── IActivityManager.java │ │ │ ├── IActivityTaskManager.java │ │ │ ├── IAppTask.java │ │ │ ├── INotificationManager.java │ │ │ ├── ITaskStackListener.java │ │ │ └── PendingIntentHidden.java │ │ ├── content │ │ │ ├── IIntentReceiver.java │ │ │ └── IIntentSender.java │ │ ├── os │ │ │ ├── Parcel.java │ │ │ ├── Parcelable.java │ │ │ ├── SELinux.java │ │ │ ├── ServiceManager.java │ │ │ └── UserHandleHidden.java │ │ ├── service │ │ │ └── notification │ │ │ │ ├── INotificationListener.java │ │ │ │ ├── IStatusBarNotificationHolder.java │ │ │ │ ├── NotificationRankingUpdate.java │ │ │ │ └── NotificationStats.java │ │ ├── view │ │ │ ├── DisplayHidden.java │ │ │ ├── DisplayShapeHidden.java │ │ │ ├── IRotationWatcher.java │ │ │ ├── IWindowManager.java │ │ │ ├── SurfaceControlHidden.java │ │ │ ├── WindowInsetsHidden.java │ │ │ ├── WindowManagerHidden.java │ │ │ └── WindowManagerPolicyConstants.java │ │ └── window │ │ │ └── TaskSnapshot.java │ │ └── com │ │ └── android │ │ ├── internal │ │ ├── content │ │ │ └── ReferrerIntent.java │ │ └── statusbar │ │ │ └── IStatusBarService.java │ │ └── server │ │ ├── SystemServer.java │ │ ├── SystemService.java │ │ ├── display │ │ ├── DisplayAdapter.java │ │ ├── DisplayControl.java │ │ ├── DisplayDevice.java │ │ ├── DisplayDeviceInfo.java │ │ ├── DisplayDeviceRepository.java │ │ ├── DisplayManagerService.java │ │ ├── DisplayModeDirector.java │ │ ├── LogicalDisplay.java │ │ └── LogicalDisplayMapper.java │ │ ├── input │ │ └── InputManagerService.java │ │ └── wm │ │ └── WindowManagerService.java │ └── test │ └── java │ └── io │ └── sunshine0523 │ └── hidden │ └── ExampleUnitTest.kt ├── service ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── sunshine0523 │ │ └── freeform │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── aidl │ │ └── io │ │ └── sunshine0523 │ │ └── freeform │ │ ├── IMiFreeformDisplayCallback.aidl │ │ └── IMiFreeformUIService.aidl │ └── test │ └── java │ └── io │ └── sunshine0523 │ └── freeform │ └── ExampleUnitTest.kt └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /app/libs/XposedBridgeAPI-89.jar 17 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_8_28_17_28_[Changes]1/shelved.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_8_28_17_28_[Changes]1/shelved.patch -------------------------------------------------------------------------------- /.idea/shelf/Uncommitted_changes_before_Checkout_at_2023_8_28_17_28__Changes_.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mi-Freeform 2 | 3 | ![Star](https://img.shields.io/github/stars/sunshine0523/Mi-Freeform) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/sunshine0523/Mi-FreeForm)](https://github.com/sunshine0523/Mi-Freeform/releases) ![License](https://img.shields.io/github/license/sunshine0523/Mi-Freeform) [![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/+8M3IrjRFiPE2NGE9) [![Chat](https://img.shields.io/badge/Join-QQ%E9%A2%91%E9%81%93-red?logo=tencent-qq&logoColor=red)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=XKL1t&from=246610&biz=ka) 4 | 5 | [中文](./README_zh-CN.md) 6 | 7 | Mi-Freeform 3 is an Android third-party freeform software that supports Android 8.1-Android 13. 8 | 9 | ## TODO 10 | 11 | - Compatible with Android 8.1-Android 9 ⭐⭐⭐⭐⭐ 12 | - ✅Redo the sidebar trigger way ⭐⭐⭐⭐ 13 | - Sliding style window ⭐⭐⭐ 14 | - Takeover notification ⭐⭐ 15 | - Start in multitasking ⭐ 16 | 17 | ## Defect 18 | 19 | - Touch sampling rate 20 | - Some apps will reload after restoring to the default screen 21 | 22 | ## Star History 23 | 24 | [![Star History Chart](https://api.star-history.com/svg?repos=sunshine0523/Mi-Freeform&type=Date)](https://star-history.com/#sunshine0523/Mi-Freeform&Date) 25 | 26 | ## License 27 | 28 | ``` 29 | Copyright (C) 2021-2023 sunshine0523 30 | 31 | This program is free software: you can redistribute it and/or modify 32 | it under the terms of the GNU General Public License as published by 33 | the Free Software Foundation, either version 3 of the License, or 34 | (at your option) any later version. 35 | 36 | This program is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU General Public License for more details. 40 | ``` 41 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # 米窗3-全局小窗 2 | 3 | ![Star](https://img.shields.io/github/stars/sunshine0523/Mi-Freeform) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/sunshine0523/Mi-FreeForm)](https://github.com/sunshine0523/Mi-Freeform/releases) ![License](https://img.shields.io/github/license/sunshine0523/Mi-Freeform) [![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/+8M3IrjRFiPE2NGE9) [![Chat](https://img.shields.io/badge/Join-QQ%E9%A2%91%E9%81%93-red?logo=tencent-qq&logoColor=red)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=XKL1t&from=246610&biz=ka) 4 | 5 | [English](./README.md) 6 | 7 | 米窗3是一款Android第三方小窗软件,支持Android 8.1-Android 13。 8 | 9 | ## TODO 10 | 11 | - 兼容Android 8.1-Android 9 ⭐⭐⭐⭐⭐ 12 | - ✅侧边栏触发方式重做 ⭐⭐⭐⭐ 13 | - 滑动样式小窗界面 ⭐⭐⭐ 14 | - 接管通知 ⭐⭐ 15 | - 多任务中启动 ⭐ 16 | 17 | ## 缺陷 18 | 19 | - 触控采样率 20 | - 还原到默认屏幕后部分应用会重载 21 | 22 | ## License 23 | 24 | ``` 25 | Copyright (C) 2021-2023 sunshine0523 26 | 27 | This program is free software: you can redistribute it and/or modify 28 | it under the terms of the GNU General Public License as published by 29 | the Free Software Foundation, either version 3 of the License, or 30 | (at your option) any later version. 31 | 32 | This program is distributed in the hope that it will be useful, 33 | but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | GNU General Public License for more details. 36 | ``` 37 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release/ 3 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.androidApplication) 4 | alias(libs.plugins.kotlinAndroid) 5 | alias(libs.plugins.ksp) 6 | alias(libs.plugins.hiddenApiRefine) 7 | } 8 | 9 | android { 10 | namespace = "com.sunshine.freeform" 11 | defaultConfig { 12 | applicationId = "com.sunshine.freeform" 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | vectorDrawables { 16 | useSupportLibrary = true 17 | } 18 | } 19 | buildTypes { 20 | release { 21 | isMinifyEnabled = true 22 | proguardFiles( 23 | getDefaultProguardFile("proguard-android-optimize.txt"), 24 | "proguard-rules.pro" 25 | ) 26 | } 27 | } 28 | buildFeatures { 29 | compose = true 30 | viewBinding = true 31 | } 32 | composeOptions { 33 | kotlinCompilerExtensionVersion = "1.4.3" 34 | } 35 | packaging { 36 | resources { 37 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 38 | } 39 | } 40 | } 41 | 42 | configurations.configureEach { 43 | exclude(group = "androidx.appcompat", module = "appcompat") 44 | } 45 | 46 | dependencies { 47 | implementation(libs.core.ktx) 48 | implementation(libs.lifecycle.viewmodel.ktx) 49 | implementation(libs.activity.compose) 50 | implementation(platform(libs.compose.bom)) 51 | implementation(libs.ui) 52 | implementation(libs.ui.graphics) 53 | implementation(libs.ui.tooling.preview) 54 | implementation(libs.compose.material3) 55 | implementation(libs.compose.foundation) 56 | implementation(libs.compose.livedata) 57 | implementation(libs.appcompat) 58 | implementation(libs.material) 59 | implementation(libs.constraintlayout) 60 | implementation(libs.room.runtime) 61 | implementation(platform(libs.compose.bom)) 62 | implementation(platform(libs.compose.bom)) 63 | implementation(libs.hiddenapirefineruntime) 64 | androidTestImplementation(platform(libs.compose.bom)) 65 | androidTestImplementation(platform(libs.compose.bom)) 66 | ksp(libs.room.compiler) 67 | implementation(libs.room.ktx) 68 | implementation(libs.hiddenapibypass) 69 | implementation(libs.systemuicontroller) 70 | implementation(projects.service) 71 | implementation(platform(libs.compose.bom)) 72 | implementation(libs.glide) 73 | implementation(libs.drawablepainter) 74 | implementation(libs.gson) 75 | androidTestImplementation(platform(libs.compose.bom)) 76 | 77 | compileOnly(files("libs/XposedBridgeAPI-89.jar")) 78 | compileOnly(projects.hiddenApi) 79 | 80 | testImplementation(libs.junit) 81 | androidTestImplementation(libs.androidx.test.ext.junit) 82 | androidTestImplementation(libs.espresso.core) 83 | androidTestImplementation(platform(libs.compose.bom)) 84 | androidTestImplementation(libs.ui.test.junit4) 85 | debugImplementation(libs.ui.tooling) 86 | debugImplementation(libs.ui.test.manifest) 87 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class * implements android.os.IInterface {*;} 24 | -keep class com.sunshine.freeform.ui.main.MainActivity 25 | -keep class com.sunshine.freeform.ui.main.MainActivityKt -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sunshine/freeform/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.sunshine.freeform", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 76 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/app/src/main/assets/xposed_init -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/MiFreeform.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import android.app.Application 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import org.lsposed.hiddenapibypass.HiddenApiBypass 7 | 8 | class MiFreeform: Application() { 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | MiFreeformServiceManager.init() 13 | } 14 | 15 | companion object { 16 | private const val TAG = "Mi-Freeform" 17 | const val CONFIG = "config" 18 | 19 | init { 20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 21 | HiddenApiBypass.addHiddenApiExemptions("") 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/MiFreeformServiceManager.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import android.app.PendingIntent 4 | import android.os.Build 5 | import android.os.IBinder 6 | import android.os.ServiceManager 7 | import android.util.Log 8 | import com.google.gson.Gson 9 | import com.sunshine.freeform.ui.main.RemoteSettings 10 | import io.sunshine0523.freeform.IMiFreeformUIService 11 | import org.lsposed.hiddenapibypass.HiddenApiBypass 12 | import java.util.Date 13 | 14 | object MiFreeformServiceManager { 15 | private const val TAG = "MiFreeformServiceManager" 16 | private var iMiFreeformService: IMiFreeformUIService? = null 17 | private val gson = Gson() 18 | 19 | fun init() { 20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 21 | try { 22 | val serviceManager = Class.forName("android.os.ServiceManager") 23 | val r = HiddenApiBypass.invoke(serviceManager, null, "getService", "mi_freeform") as IBinder 24 | Log.i(TAG, "mfs $r") 25 | iMiFreeformService = IMiFreeformUIService.Stub.asInterface(r) 26 | iMiFreeformService?.ping() 27 | } catch (e: Exception) { 28 | Log.e(TAG, "$e") 29 | e.printStackTrace() 30 | } 31 | } else { 32 | try { 33 | val r = ServiceManager.getService("mi_freeform") 34 | Log.i(TAG, "mfs $r") 35 | iMiFreeformService = IMiFreeformUIService.Stub.asInterface(r) 36 | iMiFreeformService?.ping() 37 | } catch (e: Exception) { 38 | Log.e(TAG, "$e") 39 | e.printStackTrace() 40 | } 41 | } 42 | } 43 | 44 | fun ping(): Boolean { 45 | return try { 46 | iMiFreeformService!!.ping() 47 | true 48 | } catch (e: Exception) { 49 | Log.e(TAG, "$e") 50 | e.printStackTrace() 51 | false 52 | } 53 | } 54 | 55 | fun createWindow(packageName: String, activityName: String, userId: Int, width: Int, height: Int, densityDpi: Int) { 56 | iMiFreeformService?.startAppInFreeform( 57 | packageName, 58 | activityName, 59 | userId, 60 | null, 61 | width, 62 | height, 63 | densityDpi, 64 | 120.0f, 65 | false, 66 | true, 67 | false, 68 | "com.sunshine.freeform", 69 | "view_freeform" 70 | ) 71 | } 72 | 73 | fun createWindow(pendingIntent: PendingIntent?, width: Int, height: Int, densityDpi: Int) { 74 | iMiFreeformService?.startAppInFreeform( 75 | pendingIntent?.creatorPackage?:"pendingIntentCreatorPackage", 76 | "unknownActivity-${Date().time}", 77 | -100, 78 | pendingIntent, 79 | width, 80 | height, 81 | densityDpi, 82 | 120.0f, 83 | false, 84 | true, 85 | false, 86 | "com.sunshine.freeform", 87 | "view_freeform" 88 | ) 89 | } 90 | 91 | fun getSetting(): String? { 92 | return iMiFreeformService?.settings 93 | } 94 | 95 | fun setSetting(setting: RemoteSettings) { 96 | iMiFreeformService?.settings = gson.toJson(setting) 97 | } 98 | 99 | fun removeFreeform(freeformId: String) { 100 | iMiFreeformService?.removeFreeform(freeformId) 101 | } 102 | 103 | fun getLog(): String { 104 | return iMiFreeformService?.log ?: "Maybe Mi-Freeform can`t link mi_freeform service. You can get log at /data/system/mi_freeform/log.log" 105 | } 106 | 107 | fun clearLog() { 108 | iMiFreeformService?.clearLog() 109 | } 110 | 111 | fun collapseStatusBar() { 112 | iMiFreeformService?.collapseStatusBar() 113 | } 114 | 115 | fun cancelNotification(key: String?) { 116 | iMiFreeformService?.cancelNotification(key) 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/hook/utils/XLog.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.hook.utils 2 | 3 | import de.robv.android.xposed.XposedBridge 4 | 5 | object XLog { 6 | 7 | fun d(s: Any) { 8 | XposedBridge.log("[Mi-Freeform/D] $s") 9 | } 10 | 11 | fun e(s: Any) { 12 | XposedBridge.log("[Mi-Freeform/E] $s") 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/receiver/BootReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.sunshine.freeform.MiFreeform 7 | import com.sunshine.freeform.service.SidebarService 8 | import java.util.logging.Handler 9 | 10 | /** 11 | * @author KindBrave 12 | * @since 2023/9/19 13 | */ 14 | class BootReceiver : BroadcastReceiver() { 15 | companion object { 16 | private const val BOOT = "android.intent.action.BOOT_COMPLETED" 17 | } 18 | override fun onReceive(context: Context, intent: Intent) { 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/receiver/StartFreeformReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.sunshine.freeform.MiFreeform 7 | import com.sunshine.freeform.MiFreeformServiceManager 8 | import com.sunshine.freeform.utils.Debug 9 | import com.sunshine.freeform.utils.Logger 10 | import kotlin.math.roundToInt 11 | 12 | /** 13 | * @author KindBrave 14 | * @since 2023/9/19 15 | */ 16 | class StartFreeformReceiver : BroadcastReceiver() { 17 | private val logger = Logger(TAG) 18 | companion object { 19 | private const val TAG = "StartFreeformReceiver" 20 | private const val ACTION = "com.sunshine.freeform.start_freeform" 21 | } 22 | override fun onReceive(context: Context, intent: Intent) { 23 | if (intent.action == ACTION) { 24 | if (Debug.isDebug) logger.d("onReceive ${intent.extras}") 25 | val packageName = intent.getStringExtra("packageName") 26 | val activityName = intent.getStringExtra("activityName") 27 | val userId = intent.getIntExtra("userId", 0) 28 | 29 | if (packageName != null && activityName != null) { 30 | val sp = context.getSharedPreferences(MiFreeform.CONFIG, Context.MODE_PRIVATE) 31 | val screenWidth = context.resources.displayMetrics.widthPixels 32 | val screenHeight = context.resources.displayMetrics.heightPixels 33 | val screenDensityDpi = context.resources.displayMetrics.densityDpi 34 | MiFreeformServiceManager.createWindow( 35 | packageName, 36 | activityName, 37 | userId, 38 | sp.getInt("freeform_width", (screenWidth * 0.8).roundToInt()), 39 | sp.getInt("freeform_height", (screenHeight * 0.5).roundToInt()), 40 | sp.getInt("freeform_dpi", screenDensityDpi), 41 | ) 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/DatabaseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.LiveData 5 | import com.sunshine.freeform.room.MyDatabase.Companion.getDatabase 6 | import kotlinx.coroutines.flow.Flow 7 | import java.lang.Exception 8 | 9 | /** 10 | * @author sunshine 11 | * @date 2021/1/31 12 | */ 13 | class DatabaseRepository(context: Context) { 14 | 15 | private val freeFormAppsDao: FreeFormAppsDao 16 | 17 | fun insertFreeForm(packageName: String, activityName: String, userId: Int) { 18 | try { 19 | freeFormAppsDao.insert(packageName, activityName, userId) 20 | }catch (e: Exception) { } 21 | } 22 | 23 | fun deleteFreeForm(packageName: String, activityName: String, userId: Int) { 24 | freeFormAppsDao.delete(packageName, activityName, userId) 25 | } 26 | 27 | fun getAllFreeFormName(): LiveData?> { 28 | return freeFormAppsDao.getAllName() 29 | } 30 | 31 | fun getAllFreeForm() : LiveData?> { 32 | return freeFormAppsDao.getAll() 33 | } 34 | 35 | fun getAllFreeFormAppsByFlow(): Flow?> { 36 | return freeFormAppsDao.getAllByFlow() 37 | } 38 | 39 | fun getCount(): Int { 40 | return freeFormAppsDao.getCount() 41 | } 42 | 43 | fun update(entity: FreeFormAppsEntity) { 44 | freeFormAppsDao.update(entity) 45 | } 46 | 47 | fun getAllFreeFormWithoutLiveData() : List? { 48 | return freeFormAppsDao.getAllWithoutLiveData() 49 | } 50 | 51 | fun deleteAllFreeForm() { 52 | freeFormAppsDao.deleteAll() 53 | } 54 | 55 | fun deleteMore(freeFormAppsEntityList: List) { 56 | freeFormAppsDao.deleteList(freeFormAppsEntityList) 57 | } 58 | 59 | init { 60 | val database = getDatabase(context) 61 | freeFormAppsDao = database.freeFormAppsDao 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/FreeFormAppsDao.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Query 7 | import androidx.room.Update 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/1/31 13 | */ 14 | @Dao 15 | interface FreeFormAppsDao { 16 | 17 | @Query("INSERT INTO FreeFormAppsEntity(packageName, activityName, userId) VALUES(:packageName, :activityName, :userId)") 18 | fun insert(packageName: String, activityName: String, userId: Int) 19 | 20 | @Query("DELETE FROM FreeFormAppsEntity WHERE packageName = :packageName and activityName = :activityName and userId = :userId") 21 | fun delete(packageName: String, activityName: String, userId: Int) 22 | 23 | @Query("SELECT * FROM FreeFormAppsEntity") 24 | fun getAll() : LiveData?> 25 | 26 | @Query("SELECT * FROM FreeFormAppsEntity") 27 | fun getAllByFlow() : Flow?> 28 | 29 | @Query("SELECT packageName FROM FreeFormAppsEntity") 30 | fun getAllName() : LiveData?> 31 | 32 | @Query("SELECT * FROM FreeFormAppsEntity") 33 | fun getAllWithoutLiveData() : List? 34 | 35 | @Query("SELECT COUNT(*) FROM FreeFormAppsEntity") 36 | fun getCount(): Int 37 | 38 | @Query("DELETE FROM FreeFormAppsEntity") 39 | fun deleteAll() 40 | 41 | @Delete 42 | fun deleteList(freeFormAppsEntityList: List) 43 | 44 | @Update 45 | fun update(entity: FreeFormAppsEntity) 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/FreeFormAppsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | /** 8 | * @author sunshine 9 | * @date 2021/1/31 10 | * 开启小窗数据库实体类 11 | */ 12 | @Entity 13 | class FreeFormAppsEntity( 14 | //用于排序 15 | @PrimaryKey(autoGenerate = true) 16 | val sortNum: Int, 17 | var packageName: String, 18 | var activityName: String, 19 | var userId: Int = 0 20 | ) { 21 | override fun toString(): String { 22 | return "FreeFormAppsEntity(sortNum=$sortNum, packageName='$packageName')" 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/MyDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.migration.Migration 8 | import androidx.sqlite.db.SupportSQLiteDatabase 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/1/31 13 | */ 14 | @Database(entities = [FreeFormAppsEntity::class], version = 5, exportSchema = false) 15 | abstract class MyDatabase : RoomDatabase() { 16 | abstract val freeFormAppsDao: FreeFormAppsDao 17 | 18 | companion object { 19 | private var database: MyDatabase? = null 20 | 21 | @Synchronized 22 | fun getDatabase(context: Context): MyDatabase { 23 | if (database == null) { 24 | database = Room.databaseBuilder(context.applicationContext, MyDatabase::class.java, "database.db") 25 | .allowMainThreadQueries() 26 | .build() 27 | } 28 | return database!! 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/service/FloatingService.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.service 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.Binder 6 | import android.os.IBinder 7 | 8 | class FloatingService : Service() { 9 | 10 | private lateinit var viewModel: ServiceViewModel 11 | private var isShowingSidebar = false 12 | 13 | override fun onBind(intent: Intent): IBinder { 14 | viewModel = ServiceViewModel(this.application) 15 | return MyBinder() 16 | } 17 | 18 | fun getViewModel(): ServiceViewModel { 19 | return viewModel 20 | } 21 | 22 | fun getShowingSidebar(): Boolean { 23 | return isShowingSidebar 24 | } 25 | 26 | fun setShowingSidebar(isShowingSidebar: Boolean) { 27 | this.isShowingSidebar = isShowingSidebar 28 | } 29 | 30 | inner class MyBinder : Binder() { 31 | fun getService(): FloatingService { 32 | return this@FloatingService 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/service/QuickStartTileService.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.service 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.service.quicksettings.TileService 6 | import android.util.Log 7 | import com.sunshine.freeform.MiFreeformServiceManager 8 | import com.sunshine.freeform.ui.floating.FloatingActivity 9 | import com.sunshine.freeform.ui.main.LogWidget 10 | import kotlinx.coroutines.DelicateCoroutinesApi 11 | import java.lang.reflect.Method 12 | 13 | /** 14 | * @author sunshine 15 | * @date 2021/2/27 16 | */ 17 | @DelicateCoroutinesApi 18 | class QuickStartTileService : TileService() { 19 | 20 | override fun onClick() { 21 | super.onClick() 22 | startActivity(Intent(this, FloatingActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) 23 | MiFreeformServiceManager.collapseStatusBar() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/service/SidebarService.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.service 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.PixelFormat 7 | import android.os.IBinder 8 | import android.view.View 9 | import android.view.WindowManager 10 | import android.view.WindowManagerHidden 11 | import androidx.appcompat.content.res.AppCompatResources 12 | import com.sunshine.freeform.R 13 | 14 | class SidebarService : Service() { 15 | 16 | private lateinit var viewModel: ServiceViewModel 17 | private lateinit var windowManager: WindowManager 18 | private lateinit var sideLineView: View 19 | 20 | override fun onBind(intent: Intent): IBinder? { 21 | return null 22 | } 23 | 24 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 25 | viewModel = ServiceViewModel(application) 26 | windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager 27 | if (viewModel.getBooleanSp("sideline", false)) initSideLine() 28 | initSideLine() 29 | return START_STICKY 30 | } 31 | 32 | /** 33 | * 启动侧边条 34 | */ 35 | private fun initSideLine() { 36 | val screenWidth = resources.displayMetrics.widthPixels 37 | val screenHeight = resources.displayMetrics.heightPixels 38 | val sideLinePosition = viewModel.getIntSp("sideline_position", -1) 39 | sideLineView = View(this) 40 | sideLineView.background = AppCompatResources.getDrawable(this, R.drawable.bar_corners_bg) 41 | runCatching { 42 | val layoutParams = WindowManagerHidden.LayoutParams() 43 | layoutParams.apply { 44 | type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 45 | width = 100 46 | height = 100 47 | x = sideLinePosition * screenWidth / 2 48 | y = -screenHeight / 6 49 | flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 50 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 51 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or 52 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 53 | //privateFlags = WindowManagerHidden.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_USE_BLAST or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 54 | format = PixelFormat.RGBA_8888 55 | } 56 | windowManager.addView(sideLineView, layoutParams) 57 | }.onFailure { 58 | it.printStackTrace() 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/systemapi/UserHandle.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.systemapi 2 | 3 | import android.os.UserHandle 4 | import android.util.Log 5 | 6 | /** 7 | * @author sunshine 8 | * @date 2021/6/5 9 | */ 10 | object UserHandle { 11 | 12 | /** 13 | * 通过uid获取userId 14 | */ 15 | fun getUserId(userHandle: UserHandle, uid: Int): Int { 16 | return try { 17 | userHandle::class.java.getMethod("getUserId", Int::class.javaPrimitiveType).invoke(userHandle, uid) as Int 18 | } catch (e: Exception) { 19 | 0 20 | } 21 | } 22 | 23 | fun getUserId(userHandle: UserHandle): Int { 24 | return try { 25 | val mHandleField = userHandle::class.java.getDeclaredField("mHandle") 26 | mHandleField.isAccessible = true 27 | mHandleField.get(userHandle) as Int 28 | } catch (e: Exception) { 29 | e.printStackTrace() 30 | 0 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/ProcessSurfaceView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ComponentName 5 | import android.content.Context 6 | import android.content.ServiceConnection 7 | import android.hardware.display.DisplayManager 8 | import android.os.IBinder 9 | import android.util.AttributeSet 10 | import android.util.Log 11 | import android.view.Surface 12 | import android.view.SurfaceHolder 13 | import android.view.SurfaceView 14 | import org.lsposed.hiddenapibypass.HiddenApiBypass 15 | import java.util.concurrent.atomic.AtomicBoolean 16 | 17 | /** 18 | * 继承了 SurfaceView,提供跨进程渲染的 Surface。 19 | */ 20 | @SuppressLint("ClickableViewAccessibility") 21 | class ProcessSurfaceView : SurfaceView, SurfaceHolder.Callback, ServiceConnection { 22 | private var surface: Surface? = null 23 | //private var iRemoteDraw: IRemoteDraw? = null 24 | private val isSetSurface = AtomicBoolean(false) 25 | 26 | private val displayManager: DisplayManager 27 | 28 | constructor(context: Context): super(context) 29 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 30 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) 31 | 32 | init { 33 | holder.addCallback(this@ProcessSurfaceView) 34 | holder.setFixedSize(480, 720) 35 | displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager 36 | 37 | setOnTouchListener { _, motionEvent -> 38 | Log.i(TAG, "$motionEvent") 39 | true 40 | } 41 | } 42 | 43 | override fun surfaceCreated(p0: SurfaceHolder) { 44 | surface = p0.surface 45 | setSurfaceToRemote() 46 | Log.i(TAG, "surfaceCreated") 47 | } 48 | 49 | override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) { 50 | Log.i(TAG, "surfaceChanged") 51 | } 52 | 53 | override fun surfaceDestroyed(p0: SurfaceHolder) { 54 | Log.i(TAG, "surfaceDestroyed") 55 | isSetSurface.set(false) 56 | } 57 | 58 | override fun onServiceConnected(p0: ComponentName?, iBinder: IBinder?) { 59 | Log.i(TAG, "onServiceConnected") 60 | if (iBinder == null) { 61 | Log.e(TAG, "onServiceDisconnected: iBinder is null.") 62 | return 63 | } 64 | //iRemoteDraw = IRemoteDraw.Stub.asInterface(iBinder) 65 | setSurfaceToRemote() 66 | } 67 | 68 | override fun onServiceDisconnected(p0: ComponentName?) { 69 | Log.e(TAG, "onServiceDisconnected.") 70 | } 71 | 72 | 73 | 74 | 75 | // private fun bindService() { 76 | // val intent = Intent(context, RemoteDrawService::class.java) 77 | // context.bindService(intent, this@ProcessSurfaceView, Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT) 78 | // } 79 | 80 | private fun setSurfaceToRemote() { 81 | if (isSetSurface.get()) { 82 | Log.i(TAG, "setSurfaceToRemote: has set surface.") 83 | return 84 | } 85 | if (surface != null) { 86 | val serviceManager = Class.forName("android.os.ServiceManager") 87 | val r = HiddenApiBypass.invoke(serviceManager, null, "getService", "mi_freeform") as IBinder 88 | Log.e(TAG, "mfd $r") 89 | //val s = IMiFreeformService.Stub.asInterface(r) 90 | // val callback = object : IMiFreeformDisplayCallback.Stub() { 91 | // override fun onPaused() { 92 | // 93 | // } 94 | // 95 | // override fun onResumed() { 96 | // 97 | // } 98 | // 99 | // override fun onStopped() { 100 | // 101 | // } 102 | // 103 | // override fun onDisplayAdd(displayId: Int) { 104 | // Log.i(TAG, "displayId $displayId") 105 | // } 106 | // } 107 | // s.createFreeform( 108 | // "test3", 109 | // callback, 110 | // 1080, 111 | // 1920, 112 | // 330, 113 | // true, 114 | // false, 115 | // true, 116 | // surface, 117 | // 120.0f, 118 | // 21666666L 119 | // ) 120 | // //displayManager.createVirtualDisplay("name", 480, 720, 160, surface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) 121 | // isSetSurface.set(true) 122 | } 123 | // if (iRemoteDraw != null && surface != null) { 124 | // iRemoteDraw!!.setSurface(surface!!) 125 | // isSetSurface.set(true) 126 | // } 127 | } 128 | 129 | companion object { 130 | private const val TAG = "ProcessSurfaceView" 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/app_list/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.app_list 2 | 3 | import android.content.ComponentName 4 | import android.graphics.drawable.Drawable 5 | 6 | data class AppInfo( 7 | val label: String, 8 | val icon: Drawable, 9 | val componentName: ComponentName, 10 | val userId: Int, 11 | // is add to freeform app, use for FreeformAppActivity 12 | var isFreeformApp: Boolean = false 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/app_list/SearchView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.app_list 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.CircleShape 10 | import androidx.compose.foundation.text.BasicTextField 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.mutableStateOf 16 | import androidx.compose.runtime.remember 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.text.TextStyle 22 | import androidx.compose.ui.unit.dp 23 | import com.sunshine.freeform.R 24 | import com.sunshine.freeform.service.ServiceViewModel 25 | 26 | /** 27 | * @author KindBrave 28 | * @since 2023/8/29 29 | */ 30 | @Composable 31 | fun SearchWidget(textStyle: TextStyle = TextStyle.Default, viewModel: ServiceViewModel) { 32 | var text by remember { mutableStateOf("") } 33 | Box { 34 | BasicTextField( 35 | value = text, 36 | onValueChange = { 37 | text = it 38 | viewModel.filterApp(text) 39 | }, 40 | textStyle = textStyle, 41 | modifier = Modifier 42 | .padding(20.dp) 43 | .background(MaterialTheme.colorScheme.primaryContainer, CircleShape) 44 | .height(60.dp) 45 | .fillMaxWidth(), 46 | decorationBox = { 47 | Row(verticalAlignment = Alignment.CenterVertically, 48 | modifier = Modifier.padding(horizontal = 8.dp)) { 49 | Box(modifier = Modifier 50 | .padding(horizontal = 10.dp) 51 | .weight(1f), 52 | contentAlignment = Alignment.CenterStart) { 53 | if (text.isEmpty()) { 54 | Text( 55 | text = stringResource(id = R.string.search_app), 56 | style = textStyle 57 | ) 58 | } 59 | it() 60 | } 61 | } 62 | } 63 | ) 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating/FloatingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.sunshine.freeform.R 6 | 7 | class FloatingActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_floating) 11 | FloatingWindow(application.applicationContext, intent.getBooleanExtra("isLeft", true)) 12 | finish() 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating/FloatingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating 2 | 3 | import android.content.Context 4 | import com.sunshine.freeform.MiFreeform 5 | import com.sunshine.freeform.room.DatabaseRepository 6 | import com.sunshine.freeform.room.FreeFormAppsEntity 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlin.math.max 9 | import kotlin.math.min 10 | 11 | /** 12 | * @author sunshine 13 | * @date 2022/1/6 14 | */ 15 | class FloatingViewModel(context: Context) { 16 | private val repository = DatabaseRepository(context) 17 | private val sp = context.applicationContext.getSharedPreferences(MiFreeform.CONFIG, Context.MODE_PRIVATE) 18 | val screenWidth = min(context.resources.displayMetrics.widthPixels, context.resources.displayMetrics.heightPixels) 19 | val screenHeight = max(context.resources.displayMetrics.widthPixels, context.resources.displayMetrics.heightPixels) 20 | val screenDensityDpi = context.resources.displayMetrics.densityDpi 21 | 22 | fun getAllFreeFormApps(): Flow?> { 23 | return repository.getAllFreeFormAppsByFlow() 24 | } 25 | 26 | fun deleteNotInstall(notInstallList: List) { 27 | repository.deleteMore(notInstallList) 28 | } 29 | 30 | fun getIntSp(name: String, defaultValue: Int): Int { 31 | if (sp.contains(name).not()) sp.edit().putInt(name, defaultValue).apply() 32 | return sp.getInt(name, defaultValue) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.app.NotificationManager 4 | import android.app.PendingIntent 5 | import android.content.Context 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.util.Log 9 | import androidx.appcompat.app.AppCompatActivity 10 | import com.sunshine.freeform.MiFreeformServiceManager 11 | import com.sunshine.freeform.R 12 | import kotlin.math.roundToInt 13 | 14 | /** 15 | * @author KindBrave 16 | * @since 2023/9/11 17 | * Use for open a freeform window 18 | */ 19 | class FreeformActivity : AppCompatActivity() { 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_floating) 23 | val viewModel = FreeformViewModel(this.application) 24 | 25 | val key = intent?.getStringExtra("key") 26 | val isClearable = intent?.getBooleanExtra("isClearable", false) ?: false 27 | val pendingIntent = 28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) intent?.getParcelableExtra("pendingIntent", PendingIntent::class.java) 29 | else intent?.getParcelableExtra("pendingIntent") as PendingIntent? 30 | Log.i("Mi-Freeform", "$key $isClearable $pendingIntent ${pendingIntent?.creatorPackage}") 31 | MiFreeformServiceManager.createWindow( 32 | pendingIntent, 33 | viewModel.getIntSp("freeform_width", (viewModel.screenWidth * 0.8).roundToInt()), 34 | viewModel.getIntSp("freeform_height", (viewModel.screenHeight * 0.5).roundToInt()), 35 | viewModel.getIntSp("freeform_dpi", viewModel.screenDensityDpi), 36 | ) 37 | if (isClearable) MiFreeformServiceManager.cancelNotification(key) 38 | finish() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.lifecycle.AndroidViewModel 6 | import com.sunshine.freeform.MiFreeform 7 | import kotlin.math.max 8 | import kotlin.math.min 9 | 10 | /** 11 | * @author KindBrave 12 | * @since 2023/9/11 13 | */ 14 | class FreeformViewModel(private val application: Application) : AndroidViewModel(application) { 15 | val screenWidth = min(application.resources.displayMetrics.widthPixels, application.resources.displayMetrics.heightPixels) 16 | val screenHeight = max(application.resources.displayMetrics.widthPixels, application.resources.displayMetrics.heightPixels) 17 | val screenDensityDpi = application.resources.displayMetrics.densityDpi 18 | private val sp = application.applicationContext.getSharedPreferences(MiFreeform.CONFIG, Context.MODE_PRIVATE) 19 | 20 | fun getIntSp(name: String, defaultValue: Int): Int { 21 | return sp.getInt(name, defaultValue) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/main/HomeView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.main 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.Clear 10 | import androidx.compose.material.icons.filled.Done 11 | import androidx.compose.material3.Card 12 | import androidx.compose.material3.CardDefaults 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.MaterialTheme.colorScheme 15 | import androidx.compose.material3.Text 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.text.TextStyle 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.dp 24 | import androidx.compose.ui.unit.sp 25 | import com.sunshine.freeform.MiFreeformServiceManager 26 | import com.sunshine.freeform.R 27 | 28 | @Composable 29 | fun HomeWidget() { 30 | StatusWidget() 31 | InfoWidget() 32 | } 33 | 34 | @Composable 35 | fun StatusWidget() { 36 | Card( 37 | modifier = Modifier 38 | .fillMaxWidth() 39 | .height(125.dp) 40 | .padding(15.dp), 41 | colors = CardDefaults.cardColors( 42 | containerColor = colorScheme.primary 43 | ) 44 | ) { 45 | Row( 46 | modifier = Modifier 47 | .fillMaxSize() 48 | .padding(horizontal = 20.dp), 49 | verticalAlignment = Alignment.CenterVertically 50 | ) { 51 | Icon(if (MiFreeformServiceManager.ping()) Icons.Filled.Done else Icons.Filled.Clear, "") 52 | Text( 53 | modifier = Modifier.padding(horizontal = 5.dp), 54 | text = stringResource(if (MiFreeformServiceManager.ping()) R.string.service_running else R.string.service_not_running), 55 | color = Color.White, 56 | style = TextStyle( 57 | fontWeight = FontWeight.Bold, 58 | fontSize = 17.sp, 59 | ), 60 | ) 61 | } 62 | } 63 | } 64 | 65 | @Composable 66 | fun InfoWidget() { 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/main/LogView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.main 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.foundation.horizontalScroll 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.verticalScroll 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.filled.Delete 13 | import androidx.compose.material3.ExperimentalMaterial3Api 14 | import androidx.compose.material3.FloatingActionButton 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.Scaffold 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.runtime.livedata.observeAsState 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.unit.sp 23 | import com.sunshine.freeform.MiFreeformServiceManager 24 | 25 | /** 26 | * @author KindBrave 27 | * @since 2023/9/4 28 | */ 29 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") 30 | @Composable 31 | fun LogWidget(viewModel: MainViewModel) { 32 | val log by viewModel.log.observeAsState("") 33 | val softWrap by viewModel.logSoftWrap.observeAsState(false) 34 | val modifier = Modifier 35 | .fillMaxSize() 36 | .verticalScroll(rememberScrollState()) 37 | val horizontalScrollModifier = modifier.horizontalScroll(rememberScrollState()) 38 | 39 | Column(if (softWrap) modifier else horizontalScrollModifier) { 40 | Text( 41 | text = log, 42 | fontSize = 12.sp, 43 | softWrap = softWrap 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.main 2 | 3 | import android.R 4 | import android.app.Application 5 | import android.content.Context 6 | import android.util.Log 7 | import androidx.activity.result.ActivityResultLauncher 8 | import androidx.core.content.ContentProviderCompat 9 | import androidx.lifecycle.AndroidViewModel 10 | import androidx.lifecycle.LiveData 11 | import androidx.lifecycle.MutableLiveData 12 | import com.google.gson.Gson 13 | import com.sunshine.freeform.MiFreeform 14 | import com.sunshine.freeform.MiFreeformServiceManager 15 | import java.util.concurrent.CompletableFuture 16 | import kotlin.math.max 17 | import kotlin.math.min 18 | import kotlin.math.roundToInt 19 | 20 | /** 21 | * @author KindBrave 22 | * @since 2023/8/26 23 | */ 24 | class MainViewModel(application: Application) : AndroidViewModel(application) { 25 | private val sp = application.applicationContext.getSharedPreferences(MiFreeform.CONFIG, Context.MODE_PRIVATE) 26 | 27 | val screenWidth = min(application.resources.displayMetrics.widthPixels, application.resources.displayMetrics.heightPixels) 28 | val screenHeight = max(application.resources.displayMetrics.widthPixels, application.resources.displayMetrics.heightPixels) 29 | val screenDensityDpi = application.resources.displayMetrics.densityDpi 30 | 31 | private val remoteSetting = Gson().fromJson(MiFreeformServiceManager.getSetting(), RemoteSettings::class.java) ?: RemoteSettings() 32 | private val _enableSideBar = MutableLiveData() 33 | val enableSideBar: LiveData get() = _enableSideBar 34 | private val _showImeInFreeform = MutableLiveData() 35 | val showImeInFreeform: LiveData get() = _showImeInFreeform 36 | private val _notification = MutableLiveData() 37 | val notification: LiveData get() = _notification 38 | private val _freeformWidth = MutableLiveData() 39 | val freeformWidth: LiveData get() = _freeformWidth 40 | private val _freeformHeight = MutableLiveData() 41 | val freeformHeight: LiveData get() = _freeformHeight 42 | private val _freeformDensityDpi = MutableLiveData() 43 | val freeformDensityDpi: LiveData get() = _freeformDensityDpi 44 | 45 | private val _log = MutableLiveData() 46 | val log: LiveData get() = _log 47 | private val _logSoftWrap = MutableLiveData() 48 | val logSoftWrap: LiveData get() = _logSoftWrap 49 | 50 | init { 51 | _enableSideBar.postValue(remoteSetting.enableSideBar) 52 | _showImeInFreeform.postValue(remoteSetting.showImeInFreeform) 53 | _notification.postValue(remoteSetting.notification) 54 | _freeformWidth.postValue(sp.getInt("freeform_width", (screenWidth * 0.8).roundToInt())) 55 | _freeformHeight.postValue(sp.getInt("freeform_height", (screenHeight * 0.5).roundToInt())) 56 | _freeformDensityDpi.postValue(sp.getInt("freeform_dpi", screenDensityDpi)) 57 | _log.postValue(MiFreeformServiceManager.getLog()) 58 | _logSoftWrap.postValue(false) 59 | } 60 | 61 | fun saveRemoteSidebar(enableSideBar: Boolean) { 62 | _enableSideBar.postValue(enableSideBar) 63 | remoteSetting.enableSideBar = enableSideBar 64 | MiFreeformServiceManager.setSetting(remoteSetting) 65 | } 66 | 67 | fun saveShowImeInFreeform(showImeInFreeform: Boolean) { 68 | _showImeInFreeform.postValue(showImeInFreeform) 69 | remoteSetting.showImeInFreeform = showImeInFreeform 70 | MiFreeformServiceManager.setSetting(remoteSetting) 71 | } 72 | 73 | fun saveNotification(notification: Boolean) { 74 | _notification.postValue(notification) 75 | remoteSetting.notification = notification 76 | MiFreeformServiceManager.setSetting(remoteSetting) 77 | } 78 | 79 | fun setFreeformWidth(width: Int) { 80 | setIntSp(width, "freeform_width") 81 | _freeformWidth.postValue(width) 82 | } 83 | 84 | fun setFreeformHeight(height: Int) { 85 | setIntSp(height, "freeform_height") 86 | _freeformHeight.postValue(height) 87 | } 88 | 89 | fun setFreeformDpi(dpi: Int) { 90 | setIntSp(dpi, "freeform_dpi") 91 | _freeformDensityDpi.postValue(dpi) 92 | } 93 | 94 | fun clearLog() { 95 | _log.postValue("") 96 | MiFreeformServiceManager.clearLog() 97 | } 98 | 99 | fun setLogSoftWrap(softWrap: Boolean) { 100 | _logSoftWrap.postValue(softWrap) 101 | } 102 | 103 | private fun setIntSp(value: Int, name: String) { 104 | sp.edit().apply { 105 | putInt(name, value) 106 | apply() 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/main/RemoteSettings.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.main 2 | 3 | import androidx.annotation.Keep 4 | 5 | @Keep 6 | data class RemoteSettings( 7 | var enableSideBar: Boolean = false, 8 | var showImeInFreeform: Boolean = false, 9 | var notification: Boolean = false 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.theme 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.darkColorScheme 7 | import androidx.compose.material3.dynamicDarkColorScheme 8 | import androidx.compose.material3.dynamicLightColorScheme 9 | import androidx.compose.material3.lightColorScheme 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.DisposableEffect 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.platform.LocalContext 14 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 15 | 16 | private val DarkColorScheme = darkColorScheme( 17 | primary = Purple80, 18 | secondary = PurpleGrey80, 19 | tertiary = Pink80 20 | ) 21 | 22 | private val LightColorScheme = lightColorScheme( 23 | primary = Purple40, 24 | secondary = PurpleGrey40, 25 | tertiary = Pink40 26 | 27 | /* Other default colors to override 28 | background = Color(0xFFFFFBFE), 29 | surface = Color(0xFFFFFBFE), 30 | onPrimary = Color.White, 31 | onSecondary = Color.White, 32 | onTertiary = Color.White, 33 | onBackground = Color(0xFF1C1B1F), 34 | onSurface = Color(0xFF1C1B1F), 35 | */ 36 | ) 37 | 38 | @Composable 39 | fun MiFreeformTheme( 40 | darkTheme: Boolean = isSystemInDarkTheme(), 41 | // Dynamic color is available on Android 12+ 42 | dynamicColor: Boolean = true, 43 | content: @Composable () -> Unit 44 | ) { 45 | val colorScheme = when { 46 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 47 | val context = LocalContext.current 48 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 49 | } 50 | 51 | darkTheme -> DarkColorScheme 52 | else -> LightColorScheme 53 | } 54 | 55 | MaterialTheme( 56 | colorScheme = colorScheme, 57 | typography = Typography, 58 | content = content 59 | ) 60 | 61 | // use systemUiController to make system-ui immersion 62 | val systemUiController = rememberSystemUiController() 63 | val useDarkIcons = !isSystemInDarkTheme() 64 | DisposableEffect(systemUiController, useDarkIcons) { 65 | systemUiController.setSystemBarsColor( 66 | color = Color.Transparent, 67 | darkIcons = useDarkIcons 68 | ) 69 | onDispose {} 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/Debug.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | object Debug { 4 | const val isDebug = true 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/Extension.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | /** 4 | * @author KindBrave 5 | * @since 2023/8/29 6 | */ 7 | fun List.contains(element: T, predicate: (T, T) -> Boolean): Boolean { 8 | for (item in this) { 9 | if (predicate(item, element)) { 10 | return true 11 | } 12 | } 13 | return false 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/PackageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | import android.content.pm.LauncherApps 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.os.UserHandle 7 | import androidx.annotation.RequiresApi 8 | 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/3/22 13 | * 包相关辅助函数 14 | */ 15 | object PackageUtils { 16 | 17 | //判断是否安装了这个应用 18 | fun hasInstallThisPackage(packageName: String, packageManager: PackageManager): Boolean { 19 | return try { 20 | packageManager.getApplicationInfo(packageName, 0) 21 | true 22 | } catch (e: Exception) { 23 | false 24 | } 25 | } 26 | 27 | //判断某个用户是否安装了这个应用 28 | fun hasInstallThisPackageWithUserId( 29 | packageName: String, 30 | launcherApps: LauncherApps, 31 | userHandle: UserHandle 32 | ): Boolean { 33 | return try { 34 | launcherApps.getApplicationInfo(packageName, 0, userHandle) 35 | true 36 | } catch (e: Exception) { 37 | false 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/ServiceUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | 6 | /** 7 | * @date 2021/2/1 8 | */ 9 | object ServiceUtils { 10 | fun isServiceWork(mContext: Context, serviceName: String): Boolean { 11 | var isWork = false 12 | val myAM = mContext 13 | .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 14 | val myList: List = myAM.getRunningServices(40) 15 | if (myList.isEmpty()) { 16 | return false 17 | } 18 | for (i in myList.indices) { 19 | val mName: String = myList[i].service.className 20 | myList[i].service.className 21 | if (mName == serviceName) { 22 | isWork = true 23 | break 24 | } 25 | } 26 | return isWork 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/color_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_add.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_all.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bar_corners_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_all.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_log.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_wrap_text.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/app/src/main/res/drawable/tile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_floating.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appbar_fragment_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_floating.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 31 | 32 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_choose_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 13 | 18 | 19 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_floating.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | @color/google_grey_900 6 | @color/white 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 米窗 4 | 米窗服务正在运行 5 | 米窗服务没有运行 6 | 侧边栏 7 | 所有应用 8 | 编辑 9 | 应用列表 10 | 搜索应用 11 | 侧边栏 12 | 屏幕两侧边缘向屏幕内侧滑动启动侧边栏。在Android 11以下设备可能与返回手势冲突,请使用上下滑动手势或其他APP启动 13 | 侧边栏高度 14 | 小窗宽度 15 | 小窗高度 16 | 小窗分辨率 17 | 小窗高度必须大于宽度 18 | 选择小窗应用 19 | 设置 20 | 日志 21 | 在小窗中展示输入法 22 | 在小窗中展示输入法,仅支持Android 12+ 23 | 米窗 24 | 启动小窗 25 | 接管通知 26 | 在通知中展示小窗按钮。仅支持Android 12+。警告:使用推送服务的通知无法正确跳转,比如MIUI 27 | 请安装米窗-侧边栏软件 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #202124 11 | @color/black 12 | @color/white 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20dp 4 | 28dp 5 | 28dp 6 | 16dp 7 | 50dp 8 | 70dp 9 | 10sp 10 | 16dp 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Mi-Freeform 3 | 4 | 5 | Settings 6 | Log 7 | 8 | 9 | Mi-Freeform Service is Running 10 | Mi-Freeform Service is not Running 11 | 12 | 13 | FloatingActivity 14 | 15 | 16 | All apps 17 | Edit 18 | 19 | 20 | AppListActivity 21 | Search APPs 22 | 23 | 24 | SideBar 25 | Swipe both sides of the screen to the inside of the screen to launch the sidebar. On devices below Android 11, it may conflict with the back gesture. Please use the up and down gesture or another APP to start 26 | SideBar Height 27 | Freeform Width 28 | Freeform Height 29 | Freeform DPI 30 | Freeform`s height must bigger than width 31 | Please install Mi-Freeform - Sidebar app 32 | 33 | 34 | Choose Freeform App 35 | Show IME in Freeform 36 | Show IME in Freeform. Only support Android 12+. 37 | 38 | 39 | Freeform 40 | Open a Freeform 41 | Take over notification 42 | Display the freeform button in the notification. Only support Android 12+. Warning: use push can NOT be redirected correctly. e.g. MIUI. 43 | TestActivity 44 | 45 | First Fragment 46 | Second Fragment 47 | Next 48 | Previous 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/sunshine/freeform/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ApplicationDefaultConfig 2 | import com.android.build.api.dsl.CommonExtension 3 | import com.android.build.gradle.api.AndroidBasePlugin 4 | 5 | @Suppress("DSL_SCOPE_VIOLATION") 6 | plugins { 7 | alias(libs.plugins.androidApplication) apply false 8 | alias(libs.plugins.kotlinAndroid) apply false 9 | alias(libs.plugins.androidLibrary) apply false 10 | alias(libs.plugins.zygoteLoader) apply false 11 | alias(libs.plugins.hiddenApiRefine) apply false 12 | alias(libs.plugins.ksp) apply false 13 | } 14 | 15 | val androidTargetSdkVersion by extra(34) 16 | val androidMinSdkVersion by extra(27) 17 | val androidBuildToolsVersion by extra("34.0.0-rc3") 18 | val androidCompileSdkVersion by extra(34) 19 | val androidSourceCompatibility by extra(JavaVersion.VERSION_17) 20 | val androidTargetCompatibility by extra(JavaVersion.VERSION_17) 21 | 22 | subprojects { 23 | plugins.withType(AndroidBasePlugin::class.java) { 24 | extensions.configure(CommonExtension::class.java) { 25 | compileSdk = androidCompileSdkVersion 26 | buildToolsVersion = androidBuildToolsVersion 27 | 28 | defaultConfig { 29 | minSdk = androidMinSdkVersion 30 | if (this is ApplicationDefaultConfig) { 31 | targetSdk = androidTargetSdkVersion 32 | versionCode = 3006 33 | versionName = "3.0.6-Preview" 34 | } 35 | } 36 | 37 | lint { 38 | abortOnError = true 39 | checkReleaseBuilds = false 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility = androidSourceCompatibility 44 | targetCompatibility = androidTargetCompatibility 45 | } 46 | } 47 | } 48 | plugins.withType(JavaPlugin::class.java) { 49 | extensions.configure(JavaPluginExtension::class.java) { 50 | sourceCompatibility = androidSourceCompatibility 51 | targetCompatibility = androidTargetCompatibility 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /freeform-server/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/main/assets/system/app/Mi-Freeform/ 3 | -------------------------------------------------------------------------------- /freeform-server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.kr328.gradle.zygote.ZygoteLoader 2 | 3 | plugins { 4 | alias(libs.plugins.androidApplication) 5 | alias(libs.plugins.zygoteLoader) 6 | alias(libs.plugins.hiddenApiRefine) 7 | alias(libs.plugins.kotlinAndroid) 8 | } 9 | 10 | android { 11 | namespace = "com.sunshine.freeform" 12 | 13 | buildFeatures { 14 | aidl = true 15 | } 16 | 17 | buildTypes { 18 | release { 19 | isMinifyEnabled = false 20 | proguardFiles( 21 | getDefaultProguardFile("proguard-android-optimize.txt"), 22 | "proguard-rules.pro" 23 | ) 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation(libs.magic.library) 30 | implementation(libs.gson) 31 | implementation(projects.service) 32 | implementation(libs.hiddenapirefineruntime) 33 | compileOnly(projects.hiddenApi) 34 | compileOnly(libs.core.ktx) 35 | } 36 | 37 | zygote { 38 | val moduleId = "mi-freeform" 39 | val moduleName = "Mi-Freeform" 40 | val moduleDescription = "Mi-Freeform" 41 | val moduleAuthor = "KindBrave" 42 | val moduleEntrypoint = "io.sunshine0523.freeform.ZygoteMain" 43 | 44 | packages(ZygoteLoader.PACKAGE_SYSTEM_SERVER) 45 | 46 | riru { 47 | id = "riru-$moduleId".replace('-', '_') 48 | name = "Riru - $moduleName" 49 | } 50 | 51 | zygisk { 52 | id = "zygisk-$moduleId".replace('-', '_') 53 | name = "Zygisk - $moduleName" 54 | } 55 | 56 | all { 57 | author = moduleAuthor 58 | description = moduleDescription 59 | entrypoint = moduleEntrypoint 60 | } 61 | } -------------------------------------------------------------------------------- /freeform-server/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keepclasseswithmembers class com.sunshine.freeform.Main { 24 | public static void main(java.lang.String[]); 25 | } 26 | 27 | -keep class io.sunshine0523.freeform.util.Settings {*;} -------------------------------------------------------------------------------- /freeform-server/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /freeform-server/src/main/assets/customize.d/50-copy-mfdex-to-framework.sh: -------------------------------------------------------------------------------- 1 | ui_print "- Copy Mi-Freeform service dex to /data/system/mi_freeform" 2 | 3 | mkdir "/data/system/mi_freeform" 4 | chmod 777 "/data/system/mi_freeform" 5 | cp "$MODPATH/classes.dex" "/data/system/mi_freeform/freeform.dex" -------------------------------------------------------------------------------- /freeform-server/src/main/assets/customize.d/51-active-lsp.sh: -------------------------------------------------------------------------------- 1 | #PKGNAME="io.sunshine0523.sidebar" 2 | #LSPDDBPATH="/data/adb/lspd/config/modules_config.db" 3 | # 4 | #prepareSQL(){ 5 | # unzip $ZIPFILE sqlite3 -d $TMPDIR/ > /dev/null 6 | # chmod +x $TMPDIR/sqlite3 7 | # 8 | # SQLITEPATH="$TMPDIR/sqlite3" 9 | #} 10 | # 11 | ## runSQL "database path" "command" - then you can use $SQLRESULT to read the outcome 12 | #runSQL(){ 13 | # SQLRESULT=$($SQLITEPATH $DBPATH "$CMD") 14 | #} 15 | # 16 | ##activate PKGNAME in Lsposed 17 | #activateModuleLSPD() 18 | #{ 19 | # DBPATH=$LSPDDBPATH 20 | # 21 | # ui_print '- Trying to activate the module in Lsposed...' 22 | # 23 | # CMD="select mid from modules where module_pkg_name like \"$PKGNAME\";" && runSQL 24 | # OLDMID=$(echo $SQLRESULT | xargs) 25 | # 26 | # 27 | # if [ $(($OLDMID+0)) -gt 0 ]; then 28 | # CMD="select mid from modules where mid = $OLDMID and apk_path like \"$PKGPATH\" and enabled = 1;" && runSQL 29 | # REALMID=$(echo $SQLRESULT | xargs) 30 | # 31 | # if [ $(($REALMID+0)) = 0 ]; then 32 | # CMD="delete from scope where mid = $OLDMID;" && runSQL 33 | # CMD="delete from modules where mid = $OLDMID;" && runSQL 34 | # fi 35 | # fi 36 | # 37 | ##some commands may fail. It's OK if they do 38 | # CMD="insert into modules (\"module_pkg_name\", \"apk_path\", \"enabled\") values (\"$PKGNAME\",\"$PKGPATH\", 1);" && runSQL 39 | # 40 | # CMD="select mid as ss from modules where module_pkg_name = \"$PKGNAME\";" && runSQL 41 | # 42 | # NEWMID=$(echo $SQLRESULT | xargs) 43 | # 44 | # CMD="insert into scope (mid, app_pkg_name, user_id) values ($NEWMID, \"system\",0);" && runSQL 45 | # CMD="insert into scope (mid, app_pkg_name, user_id) values ($NEWMID, \"$PKGNAME\",0);" && runSQL 46 | #} 47 | # 48 | #prepareSQL 49 | # 50 | #if [ $(ls $LSPDDBPATH) = $LSPDDBPATH ]; then 51 | # activateModuleLSPD 52 | # ui_print ' Installation Complete!' 53 | # ui_print ' Please Reboot your device to activate' 54 | #else 55 | # ui_print ' Lsposed not found!!' 56 | # ui_print ' This module will not work without Lsposed' 57 | # ui_print ' Please:' 58 | # ui_print '- Insall Lsposed' 59 | # ui_print '- Reboot' 60 | # ui_print '- Manually enable PixelXpert in Lsposed' 61 | # ui_print '- Reboot' 62 | #fi -------------------------------------------------------------------------------- /freeform-server/src/main/assets/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | magiskpolicy --live "allow untrusted_app default_android_service service_manager find" 2 | magiskpolicy --live "allow system_server default_android_service service_manager add" -------------------------------------------------------------------------------- /freeform-server/src/main/assets/sepolicy.rule: -------------------------------------------------------------------------------- 1 | allow untrusted_app default_android_service service_manager find 2 | allow priv_app default_android_service service_manager find 3 | allow system_server default_android_service service_manager add -------------------------------------------------------------------------------- /freeform-server/src/main/assets/sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/sqlite3 -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/system/.placeholder -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/etc/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/system/etc/.placeholder -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/etc/permissions/privapp_allowlist_io_sunshine0523_sidebar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/priv-app/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/system/priv-app/.placeholder -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/priv-app/Mi-Freeform-Sidebar/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/system/priv-app/Mi-Freeform-Sidebar/app-release.apk -------------------------------------------------------------------------------- /freeform-server/src/main/assets/system/priv-app/Mi-Freeform/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/freeform-server/src/main/assets/system/priv-app/Mi-Freeform/app-release.apk -------------------------------------------------------------------------------- /freeform-server/src/main/java/com/android/server/display/MiFreeformRDisplayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.IBinder; 6 | import android.os.RemoteException; 7 | import android.util.Log; 8 | import android.util.SparseArray; 9 | import android.view.Surface; 10 | import android.view.SurfaceControlHidden; 11 | 12 | import io.sunshine0523.freeform.IMiFreeformDisplayCallback; 13 | import io.sunshine0523.freeform.util.MLog; 14 | 15 | /** 16 | * A display adapter that provides freeform displays on behalf of applications. 17 | *

18 | * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. 19 | *

20 | * This adapter only support Android Q,R 21 | */ 22 | public final class MiFreeformRDisplayAdapter extends MiFreeformDisplayAdapter { 23 | private final SparseArray mLogicalDisplays; 24 | 25 | public MiFreeformRDisplayAdapter( 26 | DisplayManagerService.SyncRoot syncRoot, 27 | Context context, 28 | Handler handler, 29 | Listener listener, 30 | SparseArray mLogicalDisplays, 31 | Handler uiHandler 32 | ) { 33 | super(syncRoot, context, handler, listener, uiHandler, TAG); 34 | this.mLogicalDisplays = mLogicalDisplays; 35 | } 36 | 37 | @Override 38 | public void createFreeformLocked(String name, IMiFreeformDisplayCallback callback, 39 | int width, int height, int densityDpi, 40 | boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, 41 | Surface surface, float refreshRate, long presentationDeadlineNanos) { 42 | synchronized (getSyncRoot()) { 43 | IBinder appToken = callback.asBinder(); 44 | FreeformFlags flags = new FreeformFlags(secure, ownContentOnly, shouldShowSystemDecorations); 45 | IBinder displayToken = SurfaceControlHidden.createDisplay(UNIQUE_ID_PREFIX + name, flags.mSecure); 46 | FreeformDisplayDevice device = new FreeformDisplayDevice(displayToken, UNIQUE_ID_PREFIX + name, width, height, densityDpi, 47 | refreshRate, presentationDeadlineNanos, 48 | flags, surface, new Callback(callback, mHandler), callback.asBinder(), true); 49 | 50 | sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); 51 | mFreeformDisplayDevices.put(appToken, device); 52 | miFreeformDisplayCallbackArrayMap.put(device, callback); 53 | 54 | mHandler.postDelayed(() -> { 55 | LogicalDisplay display = findLogicalDisplayForDevice(device); 56 | MLog.i(TAG, "findLogicalDisplayForDevice " + display); 57 | try { 58 | callback.onDisplayAdd(display.getDisplayIdLocked()); 59 | } catch (Exception ignored) { 60 | 61 | } 62 | }, 500); 63 | 64 | try { 65 | appToken.linkToDeath(device, 0); 66 | } catch (RemoteException ex) { 67 | mFreeformDisplayDevices.remove(appToken); 68 | device.destroyLocked(false); 69 | } 70 | } 71 | } 72 | 73 | private LogicalDisplay findLogicalDisplayForDevice(DisplayDevice device) { 74 | synchronized (getSyncRoot()) { 75 | final int count = mLogicalDisplays.size(); 76 | for (int i = 0; i < count; i++) { 77 | LogicalDisplay display = mLogicalDisplays.valueAt(i); 78 | if (display.getPrimaryDisplayDeviceLocked() == device) { 79 | return display; 80 | } 81 | } 82 | return null; 83 | } 84 | } 85 | 86 | @Override 87 | public void resizeFreeform(IBinder appToken, int width, int height, int densityDpi) { 88 | super.resizeFreeform(appToken, width, height, densityDpi); 89 | } 90 | 91 | @Override 92 | public void releaseFreeform(IBinder appToken) { 93 | super.releaseFreeform(appToken); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/com/android/server/display/MiFreeformUDisplayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.IBinder; 6 | import android.os.RemoteException; 7 | import android.util.Log; 8 | import android.view.DisplayShapeHidden; 9 | import android.view.Surface; 10 | 11 | import io.sunshine0523.freeform.IMiFreeformDisplayCallback; 12 | 13 | /** 14 | * A display adapter that provides freeform displays on behalf of applications. 15 | *

16 | * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. 17 | *

18 | * This adapter only support Android U 19 | */ 20 | public final class MiFreeformUDisplayAdapter extends MiFreeformDisplayAdapter { 21 | private final LogicalDisplayMapper mLogicalDisplayMapper; 22 | 23 | public MiFreeformUDisplayAdapter( 24 | DisplayManagerService.SyncRoot syncRoot, 25 | Context context, 26 | Handler handler, 27 | DisplayDeviceRepository listener, 28 | LogicalDisplayMapper logicalDisplayMapper, 29 | Handler uiHandler 30 | ) { 31 | super(syncRoot, context, handler, listener, uiHandler, TAG); 32 | mLogicalDisplayMapper = logicalDisplayMapper; 33 | } 34 | 35 | @Override 36 | public void createFreeformLocked(String name, IMiFreeformDisplayCallback callback, 37 | int width, int height, int densityDpi, 38 | boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, 39 | Surface surface, float refreshRate, long presentationDeadlineNanos) { 40 | synchronized (getSyncRoot()) { 41 | IBinder appToken = callback.asBinder(); 42 | FreeformFlags flags = new FreeformFlags(secure, ownContentOnly, shouldShowSystemDecorations); 43 | IBinder displayToken = DisplayControl.createDisplay(UNIQUE_ID_PREFIX + name, flags.mSecure, refreshRate); 44 | FreeformDisplayDevice device = new FreeformUDisplayDevice(displayToken, UNIQUE_ID_PREFIX + name, width, height, densityDpi, 45 | refreshRate, presentationDeadlineNanos, 46 | flags, surface, new Callback(callback, mHandler), callback.asBinder()); 47 | 48 | sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); 49 | mFreeformDisplayDevices.put(appToken, device); 50 | miFreeformDisplayCallbackArrayMap.put(device, callback); 51 | 52 | mHandler.postDelayed(() -> { 53 | LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); 54 | Log.i(TAG, "findLogicalDisplayForDevice " + display); 55 | try { 56 | callback.onDisplayAdd(display.getDisplayIdLocked()); 57 | } catch (Exception ignored) { 58 | 59 | } 60 | }, 500); 61 | 62 | try { 63 | appToken.linkToDeath(device, 0); 64 | } catch (RemoteException ex) { 65 | mFreeformDisplayDevices.remove(appToken); 66 | device.destroyLocked(false); 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public void resizeFreeform(IBinder appToken, int width, int height, int densityDpi) { 73 | super.resizeFreeform(appToken, width, height, densityDpi); 74 | } 75 | 76 | @Override 77 | public void releaseFreeform(IBinder appToken) { 78 | super.releaseFreeform(appToken); 79 | } 80 | 81 | private class FreeformUDisplayDevice extends FreeformDisplayDevice { 82 | 83 | FreeformUDisplayDevice(IBinder displayToken, String uniqueId, 84 | int width, int height, int density, 85 | float refreshRate, long presentationDeadlineNanos, 86 | FreeformFlags flags, Surface surface, 87 | Callback callback, IBinder appToken) { 88 | super(displayToken, uniqueId, 89 | width, height, density, 90 | refreshRate, presentationDeadlineNanos, 91 | flags, surface, callback, appToken); 92 | } 93 | 94 | @Override 95 | public DisplayDeviceInfo getDisplayDeviceInfoLocked() { 96 | super.getDisplayDeviceInfoLocked(); 97 | mInfo.displayShape = DisplayShapeHidden.createDefaultDisplayShape(mInfo.width, mInfo.height, false); 98 | 99 | return mInfo; 100 | } 101 | 102 | @Override 103 | public void destroyLocked(boolean binderAlive) { 104 | if (mSurface != null) { 105 | mSurface.release(); 106 | mSurface = null; 107 | } 108 | DisplayControl.destroyDisplay(getDisplayTokenLocked()); 109 | if (binderAlive) { 110 | mCallback.dispatchDisplayStopped(); 111 | } 112 | 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/service/MiFreeformService.java: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.service; 2 | 3 | import android.view.InputEvent; 4 | import android.view.Surface; 5 | 6 | import com.android.server.display.MiFreeformDisplayAdapter; 7 | 8 | import io.sunshine0523.freeform.IMiFreeformDisplayCallback; 9 | import io.sunshine0523.freeform.util.MLog; 10 | 11 | public class MiFreeformService { 12 | private static final String TAG = "Mi-Freeform/MiFreeformService"; 13 | 14 | private MiFreeformDisplayAdapter miFreeformDisplayAdapter = null; 15 | 16 | public MiFreeformService(MiFreeformDisplayAdapter miFreeformDisplayAdapter) { 17 | this.miFreeformDisplayAdapter = miFreeformDisplayAdapter; 18 | } 19 | 20 | public void createFreeform(String name, IMiFreeformDisplayCallback callback, 21 | int width, int height, int densityDpi, boolean secure, 22 | boolean ownContentOnly, boolean shouldShowSystemDecorations, Surface surface, 23 | float refreshRate, long presentationDeadlineNanos) { 24 | miFreeformDisplayAdapter.createFreeformLocked(name, callback, 25 | width, height, densityDpi, secure, 26 | ownContentOnly, shouldShowSystemDecorations, surface, 27 | refreshRate, presentationDeadlineNanos); 28 | MLog.i(TAG, "createFreeform"); 29 | } 30 | 31 | public void injectInputEvent(InputEvent event, int displayId) { 32 | try { 33 | event.getClass().getMethod("setDisplayId", int.class).invoke(event, displayId); 34 | SystemServiceHolder.inputManagerService.injectInputEvent(event, 0); 35 | } catch (Exception e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | public boolean isRunning() { 41 | return null != SystemServiceHolder.inputManagerService; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/service/SideBarService.java: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.service; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Handler; 6 | import android.util.Log; 7 | 8 | import io.sunshine0523.freeform.ui.sidebar.SideBarWindow; 9 | import io.sunshine0523.freeform.util.DataChangeListener; 10 | import io.sunshine0523.freeform.util.DataHelper; 11 | import io.sunshine0523.freeform.util.Settings; 12 | 13 | public class SideBarService implements DataChangeListener { 14 | private final Context context; 15 | private final Handler uiHandler; 16 | private Settings settings; 17 | private SideBarWindow sideBarWindow = null; 18 | SideBarService(Context context, Handler uiHandler, Settings settings) { 19 | this.context = context; 20 | this.uiHandler = uiHandler; 21 | this.settings = settings; 22 | 23 | // new Thread(() -> { 24 | // if (settings.getEnableSideBar()) { 25 | // // wait system ui boot 26 | // SystemServiceHolder.waitSystemService("wallpaper"); 27 | // sideBarWindow = new SideBarWindow(context, uiHandler); 28 | // } 29 | // }).start(); 30 | } 31 | 32 | @Override 33 | public void onChanged() { 34 | // settings = DataHelper.INSTANCE.getSettings(); 35 | // setSideBarStatus(settings.getEnableSideBar()); 36 | } 37 | 38 | public void setSideBarStatus(boolean show) { 39 | if (null != sideBarWindow) sideBarWindow.destroy(); 40 | if (show) sideBarWindow = new SideBarWindow(context, uiHandler); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/service/SystemServiceHolder.java: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.service; 2 | 3 | import android.app.IActivityManager; 4 | import android.app.IActivityTaskManager; 5 | import android.app.INotificationManager; 6 | import android.content.Context; 7 | import android.os.Build; 8 | import android.os.ServiceManager; 9 | import android.view.IWindowManager; 10 | 11 | import com.android.internal.statusbar.IStatusBarService; 12 | import com.android.server.input.InputManagerService; 13 | 14 | import io.sunshine0523.freeform.util.MLog; 15 | 16 | public class SystemServiceHolder { 17 | 18 | private static final String TAG = "Mi-Freeform/SystemServiceHolder"; 19 | 20 | static InputManagerService inputManagerService; 21 | public static IActivityManager activityManager; 22 | //For Q,R,S,T 23 | public static IActivityTaskManager activityTaskManager; 24 | public static IWindowManager windowManager; 25 | public static IStatusBarService statusBarService; 26 | public static INotificationManager notificationManager; 27 | 28 | static void init(ServiceCallback callback) { 29 | new Thread(() -> { 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 31 | waitSystemService("activity_task"); 32 | activityTaskManager = IActivityTaskManager.Stub.asInterface(ServiceManager.getService("activity_task")); 33 | } 34 | waitSystemService("activity"); 35 | waitSystemService("input"); 36 | waitSystemService("window"); 37 | waitSystemService("statusbar"); 38 | waitSystemService("notification"); 39 | activityManager = IActivityManager.Stub.asInterface(ServiceManager.getService("activity")); 40 | inputManagerService = (InputManagerService) ServiceManager.getService("input"); 41 | windowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); 42 | statusBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService("statusbar")); 43 | notificationManager = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); 44 | callback.allAdded(); 45 | }).start(); 46 | } 47 | 48 | public static void waitSystemService(String name) { 49 | int count = 20; 50 | try { 51 | while (count-- > 0 && null == ServiceManager.getService(name)) { 52 | Thread.sleep(1000); 53 | MLog.i(TAG, name + " not start, wait 1s"); 54 | } 55 | } catch (Exception ignored) { } 56 | } 57 | 58 | interface ServiceCallback { 59 | void allAdded(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | import android.app.PendingIntent 4 | import android.content.ComponentName 5 | 6 | data class AppConfig( 7 | val packageName: String, 8 | val activityName: String, 9 | val pendingIntent: PendingIntent?, 10 | val userId: Int 11 | ) -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/FreeformAnimation.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.animation.ValueAnimator 6 | 7 | object FreeformAnimation { 8 | fun moveInScreenAnimator(start: Int, end: Int, dur: Long, moveX: Boolean, window: FreeformWindow) { 9 | AnimatorSet().apply { 10 | play( 11 | ValueAnimator.ofInt(start, end).apply { 12 | addUpdateListener { 13 | window.windowManager.updateViewLayout( 14 | window.freeformLayout, 15 | window.windowParams.apply { 16 | if (moveX) x = it.animatedValue as Int 17 | else y = it.animatedValue as Int 18 | } 19 | ) 20 | } 21 | } 22 | ) 23 | duration = dur 24 | start() 25 | } 26 | } 27 | 28 | fun toFullScreen(window: FreeformWindow, dur: Long, listener: Animator.AnimatorListener) { 29 | AnimatorSet().apply { 30 | play( 31 | ValueAnimator.ofInt(window.windowParams.x, 0).apply { 32 | addUpdateListener { 33 | window.windowManager.updateViewLayout( 34 | window.freeformLayout, 35 | window.windowParams.apply { 36 | x = it.animatedValue as Int 37 | } 38 | ) 39 | } 40 | } 41 | ) 42 | duration = dur 43 | start() 44 | } 45 | AnimatorSet().apply { 46 | play( 47 | ValueAnimator.ofInt(window.windowParams.y, 0).apply { 48 | addUpdateListener { 49 | window.windowManager.updateViewLayout( 50 | window.freeformLayout, 51 | window.windowParams.apply { 52 | y = it.animatedValue as Int 53 | } 54 | ) 55 | } 56 | } 57 | ) 58 | duration = dur 59 | start() 60 | } 61 | AnimatorSet().apply { 62 | play( 63 | ValueAnimator.ofInt(window.freeformConfig.width, window.defaultDisplayWidth).apply { 64 | addUpdateListener { 65 | window.freeformRootView.layoutParams = window.freeformRootView.layoutParams.apply { 66 | width = it.animatedValue as Int 67 | } 68 | } 69 | } 70 | ) 71 | duration = dur 72 | start() 73 | } 74 | AnimatorSet().apply { 75 | play( 76 | ValueAnimator.ofInt(window.freeformConfig.height, window.defaultDisplayHeight).apply { 77 | addUpdateListener { 78 | window.freeformRootView.layoutParams = window.freeformRootView.layoutParams.apply { 79 | height = it.animatedValue as Int 80 | } 81 | } 82 | } 83 | ) 84 | duration = dur 85 | start() 86 | }.addListener(listener) 87 | } 88 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/FreeformConfig.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | data class FreeformConfig( 4 | var width: Int, 5 | var height: Int, 6 | var densityDpi: Int, 7 | var secure: Boolean = true, 8 | var ownContentOnly: Boolean = true, 9 | var shouldShowSystemDecorations: Boolean = true, 10 | var refreshRate: Float, 11 | var hangUpWidth: Int = 300, 12 | var hangUpHeight: Int = 400, 13 | var isHangUp: Boolean = false, 14 | // 记录挂起前的位置,以便恢复 15 | var notInHangUpX: Int = 0, 16 | var notInHangUpY: Int = 0, 17 | //小窗屏幕宽高,与view的比例 18 | var freeformWidth: Int, 19 | var freeformHeight: Int, 20 | //小窗屏幕尺寸/小窗界面尺寸 21 | var scale: Float 22 | ) { 23 | constructor(width: Int, height: Int, densityDpi: Int, secure: Boolean, ownContentOnly: Boolean, shouldShowSystemDecorations: Boolean, refreshRate: Float) : this(width, height, densityDpi, secure, ownContentOnly, shouldShowSystemDecorations, refreshRate, 300, 400, false, 0, 0, 1080, 1920, 1.0f) { 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/FreeformTextureView.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import android.view.TextureView 8 | 9 | /** 10 | * @author KindBrave 11 | * @since 2023/9/16 12 | */ 13 | class FreeformTextureView @JvmOverloads constructor( 14 | context: Context, attrs: AttributeSet? = null 15 | ) : TextureView(context, attrs) { 16 | 17 | companion object { 18 | private const val TAG = "Mi-Freeform/FreeformTextureView" 19 | } 20 | 21 | override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { 22 | return super.dispatchGenericMotionEvent(event) 23 | } 24 | 25 | override fun dispatchTouchEvent(event: MotionEvent?): Boolean { 26 | return super.dispatchTouchEvent(event) 27 | } 28 | 29 | override fun dispatchGenericPointerEvent(event: MotionEvent?): Boolean { 30 | return super.dispatchGenericPointerEvent(event) 31 | } 32 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/FreeformWindowManager.java: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.os.Handler; 7 | import android.util.ArrayMap; 8 | import android.util.Log; 9 | 10 | import java.util.HashMap; 11 | 12 | import io.sunshine0523.freeform.ui.freeform.AppConfig; 13 | import io.sunshine0523.freeform.ui.freeform.FreeformConfig; 14 | import io.sunshine0523.freeform.ui.freeform.FreeformWindow; 15 | import io.sunshine0523.freeform.ui.freeform.UIConfig; 16 | import io.sunshine0523.freeform.util.DataHelper; 17 | import io.sunshine0523.freeform.util.Settings; 18 | 19 | public class FreeformWindowManager { 20 | static String topWindow = ""; 21 | public static Settings settings = DataHelper.INSTANCE.getSettings(); 22 | private static final HashMap freeformWindows = new HashMap<>(); 23 | public static void addWindow( 24 | Handler handler, Context context, 25 | String packageName, String activityName, int userId, PendingIntent pendingIntent, 26 | int width, int height, int densityDpi, float refreshRate, 27 | boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, 28 | String resPkg, String layoutName) { 29 | AppConfig appConfig = new AppConfig(packageName, activityName, pendingIntent, userId); 30 | FreeformConfig freeformConfig = new FreeformConfig(width, height, densityDpi, secure, ownContentOnly, shouldShowSystemDecorations, refreshRate); 31 | UIConfig uiConfig = new UIConfig(resPkg, layoutName); 32 | FreeformWindow window = new FreeformWindow(handler, context, appConfig, freeformConfig, uiConfig); 33 | //if freeform exist, remove old 34 | FreeformWindow oldWindow = freeformWindows.get(window.getFreeformId()); 35 | if (oldWindow != null) oldWindow.destroy("addWindow:destroy old window", false); 36 | freeformWindows.put(window.getFreeformId(), window); 37 | } 38 | 39 | /** 40 | * @param freeformId packageName,activityName,userId 41 | */ 42 | public static void removeWindow(String freeformId) { 43 | FreeformWindow removedWindow = freeformWindows.remove(freeformId); 44 | if (removedWindow != null) removedWindow.destroy("FreeformWindowManager#removeWindow", false); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/RemoteResourceHolder.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.Context.CONTEXT_IGNORE_SECURITY 6 | import android.content.Context.CONTEXT_INCLUDE_CODE 7 | import android.util.Log 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import io.sunshine0523.freeform.util.MLog 12 | 13 | 14 | @SuppressLint("WrongConstant", "DiscouragedApi") 15 | class RemoteResourceHolder(context: Context, private val resPkg: String) { 16 | 17 | private lateinit var remoteContext: Context 18 | 19 | companion object { 20 | private const val TAG = "Mi-Freeform/RemoteResourceHolder" 21 | } 22 | 23 | init { 24 | try { 25 | remoteContext = context.createPackageContext(resPkg, CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY) 26 | } catch (e: Exception) { 27 | e.printStackTrace() 28 | MLog.e(TAG, "init", e) 29 | runCatching { remoteContext = context.createPackageContext(resPkg, CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY) } 30 | } 31 | } 32 | 33 | fun getLayout(layoutName: String): ViewGroup? { 34 | return try { 35 | val freeformLayoutId = remoteContext.resources.getIdentifier(layoutName, "layout", resPkg) 36 | val r = LayoutInflater.from(remoteContext).inflate(freeformLayoutId, null, false) 37 | if (null == r) Log.e(TAG, "can not find layout $layoutName") 38 | r as ViewGroup 39 | } catch (e: Exception) { 40 | e.printStackTrace() 41 | MLog.e(TAG, "getLayout", e) 42 | null 43 | } 44 | } 45 | 46 | fun getLayoutChildViewByTag(layout: ViewGroup, tagName: String): T? { 47 | return try { 48 | val r = layout.findViewWithTag(tagName) 49 | if (null == r) MLog.e(TAG, "can not find tag $tagName in $layout") 50 | r 51 | } catch (e: Exception) { 52 | e.printStackTrace() 53 | MLog.e(TAG, "getLayoutChildViewByTag", e) 54 | null 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/RotationWatcher.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | import android.view.IRotationWatcher 4 | 5 | /** 6 | * Default Display Rotation Listener 7 | */ 8 | class RotationWatcher(private val window: FreeformWindow): IRotationWatcher.Stub() { 9 | 10 | override fun onRotationChanged(rotation: Int) { 11 | window.defaultDisplayWidth = window.context.resources.displayMetrics.widthPixels 12 | window.defaultDisplayHeight = window.context.resources.displayMetrics.heightPixels 13 | window.handler.post { 14 | if (window.freeformConfig.isHangUp) window.toHangUp() 15 | else window.makeSureFreeformInScreen() 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/freeform/UIConfig.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.freeform 2 | 3 | data class UIConfig( 4 | val resPkg: String, 5 | val layoutName: String 6 | ) -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/sidebar/MGestureManager.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.sidebar 2 | 3 | import android.content.Context 4 | import android.view.GestureDetector 5 | import android.view.MotionEvent 6 | import android.view.GestureDetector.SimpleOnGestureListener 7 | import kotlin.math.abs 8 | 9 | class MGestureManager(context: Context, private val mListener: MGestureListener) { 10 | private val mGestureDetector: GestureDetector 11 | private val minVelocity = 50 12 | 13 | companion object { 14 | private const val TAG = "MGestureManager" 15 | } 16 | 17 | interface MGestureListener { 18 | fun singleFingerSlipAction( 19 | gestureEvent: GestureEvent?, 20 | startEvent: MotionEvent?, 21 | endEvent: MotionEvent?, 22 | velocity: Float 23 | ): Boolean 24 | } 25 | 26 | enum class GestureEvent { 27 | SINGLE_GINGER_LEFT_SLIP, SINGLE_GINGER_RIGHT_SLIP, SINGLE_GINGER_UP_SLIP, SINGLE_GINGER_DOWN_SLIP 28 | } 29 | 30 | fun onTouchEvent(event: MotionEvent): Boolean { 31 | return mGestureDetector.onTouchEvent(event) 32 | } 33 | 34 | private inner class SimpleGesture : SimpleOnGestureListener() { 35 | override fun onFling( 36 | e1: MotionEvent?, e2: MotionEvent, velocityX: Float, 37 | velocityY: Float 38 | ): Boolean { 39 | if (null == e1) return false 40 | if (e1.x - e2.x > 0 && abs((e1.x - e2.x).toInt()) > abs((e1.y - e2.y).toInt()) && abs(velocityX) > minVelocity) { 41 | return mListener.singleFingerSlipAction( 42 | GestureEvent.SINGLE_GINGER_LEFT_SLIP, 43 | e1, 44 | e2, 45 | abs(velocityX) 46 | ) 47 | } 48 | else if (e1.x - e2.x < 0 && abs((e1.x - e2.x).toInt()) > abs((e1.y - e2.y).toInt()) && abs(velocityX) > minVelocity) { 49 | return mListener.singleFingerSlipAction( 50 | GestureEvent.SINGLE_GINGER_RIGHT_SLIP, 51 | e1, 52 | e2, 53 | abs(velocityX) 54 | ) 55 | } else if (e1.y - e2.y > 0 && abs((e1.y - e2.y).toInt()) > abs((e1.x - e2.x).toInt()) && abs( 56 | velocityY 57 | ) > minVelocity 58 | ) { 59 | return mListener.singleFingerSlipAction( 60 | GestureEvent.SINGLE_GINGER_UP_SLIP, 61 | e1, 62 | e2, 63 | abs(velocityY) 64 | ) 65 | } else if (e1.y - e2.y < 0 && abs((e1.y - e2.y).toInt()) > abs((e1.x - e2.x).toInt()) && abs( 66 | velocityY 67 | ) > minVelocity 68 | ) { 69 | return mListener.singleFingerSlipAction( 70 | GestureEvent.SINGLE_GINGER_DOWN_SLIP, 71 | e1, 72 | e2, 73 | abs(velocityY) 74 | ) 75 | } else return true 76 | } 77 | } 78 | 79 | init { 80 | mGestureDetector = 81 | GestureDetector(context, SimpleGesture(), null, true) 82 | } 83 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/sidebar/SideBarTouchListener.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.sidebar 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ComponentName 5 | import android.content.Intent 6 | import android.util.Log 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import io.sunshine0523.freeform.util.MLog 10 | 11 | /** 12 | * @author KindBrave 13 | * @since 2023/8/23 14 | */ 15 | @SuppressLint("ClickableViewAccessibility") 16 | class SideBarTouchListener(private val sideBarWindow: SideBarWindow) { 17 | companion object { 18 | private const val TAG = "Mi-Freeform/SideBarTouchListener" 19 | } 20 | init { 21 | sideBarWindow.uiHandler.post { 22 | val leftGestureManager = MGestureManager(sideBarWindow.context, LeftListener()) 23 | val rightGestureManager = MGestureManager(sideBarWindow.context, RightListener()) 24 | val leftTouchListener = View.OnTouchListener { _, e -> 25 | leftGestureManager.onTouchEvent(e) 26 | true 27 | } 28 | val rightTouchListener = View.OnTouchListener { _, e -> 29 | rightGestureManager.onTouchEvent(e) 30 | true 31 | } 32 | sideBarWindow.leftView.setOnTouchListener(leftTouchListener) 33 | sideBarWindow.rightView.setOnTouchListener(rightTouchListener) 34 | } 35 | } 36 | 37 | inner class LeftListener : MGestureManager.MGestureListener { 38 | override fun singleFingerSlipAction( 39 | gestureEvent: MGestureManager.GestureEvent?, 40 | startEvent: MotionEvent?, 41 | endEvent: MotionEvent?, 42 | velocity: Float 43 | ): Boolean { 44 | if (null != gestureEvent) { 45 | val intent = Intent().apply { 46 | component = ComponentName("com.sunshine.freeform", "com.sunshine.freeform.ui.floating.FloatingActivity") 47 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 48 | action = Intent.ACTION_MAIN 49 | addCategory(Intent.CATEGORY_LAUNCHER) 50 | putExtra("isLeft", true) 51 | } 52 | runCatching { sideBarWindow.context.startActivity(intent) }.onFailure { MLog.e(TAG, "$it") } 53 | return true 54 | } 55 | return true 56 | } 57 | } 58 | 59 | inner class RightListener : MGestureManager.MGestureListener { 60 | override fun singleFingerSlipAction( 61 | gestureEvent: MGestureManager.GestureEvent?, 62 | startEvent: MotionEvent?, 63 | endEvent: MotionEvent?, 64 | velocity: Float 65 | ): Boolean { 66 | if (null != gestureEvent) { 67 | val intent = Intent().apply { 68 | component = ComponentName("com.sunshine.freeform", "com.sunshine.freeform.ui.floating.FloatingActivity") 69 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 70 | action = Intent.ACTION_MAIN 71 | addCategory(Intent.CATEGORY_LAUNCHER) 72 | putExtra("isLeft", false) 73 | } 74 | runCatching { sideBarWindow.context.startActivity(intent) }.onFailure { MLog.e(TAG, "$it") } 75 | return true 76 | } 77 | return false 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/ui/sidebar/SideBarWindow.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.ui.sidebar 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.PixelFormat 7 | import android.os.Handler 8 | import android.view.Display 9 | import android.view.IRotationWatcher 10 | import android.view.View 11 | import android.view.WindowManager 12 | import android.view.WindowManagerHidden 13 | import io.sunshine0523.freeform.service.SystemServiceHolder 14 | import io.sunshine0523.freeform.util.MLog 15 | /** 16 | * @author KindBrave 17 | * @since 2023/8/22 18 | */ 19 | class SideBarWindow( 20 | val context: Context, 21 | val uiHandler: Handler 22 | ) : IRotationWatcher.Stub() { 23 | private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager 24 | private val leftWindowParams = WindowManagerHidden.LayoutParams() 25 | private val rightWindowParams = WindowManagerHidden.LayoutParams() 26 | lateinit var leftView: View 27 | lateinit var rightView: View 28 | 29 | companion object { 30 | private const val TAG = "Mi-Freeform/SideBarWindow" 31 | } 32 | 33 | init { 34 | addSideBarView() 35 | SystemServiceHolder.windowManager.watchRotation(this, Display.DEFAULT_DISPLAY) 36 | } 37 | 38 | override fun onRotationChanged(rotation: Int) { 39 | destroy() 40 | addSideBarView(false) 41 | } 42 | 43 | @SuppressLint("ClickableViewAccessibility") 44 | private fun addSideBarView(needUpdateColor: Boolean = true) { 45 | val screenWidth = context.resources.displayMetrics.widthPixels 46 | val screenHeight = context.resources.displayMetrics.heightPixels 47 | uiHandler.post { 48 | leftView = View(context) 49 | rightView = View(context) 50 | leftView.setBackgroundColor(Color.TRANSPARENT) 51 | rightView.setBackgroundColor(Color.TRANSPARENT) 52 | SideBarTouchListener(this) 53 | leftWindowParams.apply { 54 | type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 55 | width = 100 56 | height = screenHeight / 5 57 | x = -screenWidth / 2 58 | y = -screenHeight / 6 59 | flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 60 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 61 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or 62 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 63 | privateFlags = WindowManagerHidden.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_USE_BLAST or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 64 | format = PixelFormat.RGBA_8888 65 | } 66 | rightWindowParams.apply { 67 | type = 2026 68 | width = 100 69 | height = screenHeight / 5 70 | x = screenWidth / 2 71 | y = -screenHeight / 6 72 | flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 73 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 74 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or 75 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 76 | privateFlags = WindowManagerHidden.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_USE_BLAST or WindowManagerHidden.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 77 | format = PixelFormat.RGBA_8888 78 | } 79 | uiHandler.post { 80 | runCatching { 81 | windowManager.addView(leftView, leftWindowParams) 82 | windowManager.addView(rightView, rightWindowParams) 83 | 84 | if (needUpdateColor) { 85 | updateSideBarColor(Color.BLUE) 86 | Thread { 87 | Thread.sleep(3000L) 88 | uiHandler.post { runCatching { updateSideBarColor(Color.TRANSPARENT) } } 89 | }.start() 90 | } 91 | }.onFailure { 92 | MLog.e(TAG, "$it") 93 | } 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Called in uiHandler 100 | */ 101 | private fun updateSideBarColor(color: Int) { 102 | leftView.setBackgroundColor(color) 103 | rightView.setBackgroundColor(color) 104 | windowManager.updateViewLayout(leftView, leftWindowParams) 105 | windowManager.updateViewLayout(rightView, rightWindowParams) 106 | } 107 | 108 | fun destroy() { 109 | SystemServiceHolder.windowManager.removeRotationWatcher(this) 110 | uiHandler.post { 111 | runCatching { 112 | windowManager.removeViewImmediate(leftView) 113 | windowManager.removeViewImmediate(rightView) 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/util/DataHelper.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.util 2 | 3 | import android.os.Environment 4 | import android.util.Log 5 | import com.google.gson.Gson 6 | import java.io.File 7 | import java.lang.StringBuilder 8 | 9 | object DataHelper { 10 | private const val TAG = "Mi-Freeform/DataHelper" 11 | private val dataDir = File("${Environment.getDataDirectory()}/system/mi_freeform") 12 | private val dataFile = File(dataDir, "settings.json") 13 | private val logFile = File(dataDir, "log.log") 14 | private val gson = Gson() 15 | private var settings: Settings? = null 16 | private val logs = StringBuilder() 17 | 18 | init { 19 | runCatching { 20 | dataDir.mkdir() 21 | dataFile.createNewFile() 22 | logFile.createNewFile() 23 | 24 | Thread { 25 | runCatching { 26 | logFile.writeText("") 27 | } 28 | }.start() 29 | }.onFailure { 30 | MLog.e(TAG, "$it $dataDir") 31 | } 32 | } 33 | 34 | fun getSettings(): Settings { 35 | if (null == settings) { 36 | settings = runCatching { 37 | gson.fromJson(dataFile.readText(), Settings::class.java) ?: Settings() 38 | }.getOrElse { 39 | Settings() 40 | } 41 | } 42 | return settings!! 43 | } 44 | 45 | fun getSettingsString(): String { 46 | return gson.toJson(getSettings()) 47 | } 48 | 49 | fun saveSettings(settings: Settings) { 50 | this.settings = settings 51 | runCatching { 52 | dataFile.writeText(gson.toJson(settings)) 53 | } 54 | } 55 | 56 | fun saveSettings(settings: String, listener: DataChangeListener) { 57 | runCatching { 58 | this.settings = gson.fromJson(settings, Settings::class.java) ?: this.settings 59 | dataFile.writeText(settings) 60 | listener.onChanged() 61 | } 62 | } 63 | 64 | fun getLog(): String { 65 | return logs.toString() 66 | } 67 | 68 | fun appendLog(log: String) { 69 | Thread { 70 | runCatching { 71 | logs.append(log).append("\n") 72 | logFile.appendText("$log\n") 73 | } 74 | }.start() 75 | } 76 | 77 | fun clearLog() { 78 | Thread { 79 | runCatching { 80 | logs.clear() 81 | logFile.writeText("") 82 | } 83 | }.start() 84 | } 85 | } 86 | 87 | interface DataChangeListener { 88 | fun onChanged() 89 | } -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/util/MLog.java: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.PrintWriter; 6 | import java.io.StringWriter; 7 | 8 | public class MLog { 9 | public static void i(String tag, String message) { 10 | Log.i(tag, message); 11 | DataHelper.INSTANCE.appendLog("[i] " + tag + " " + message); 12 | } 13 | 14 | public static void w(String tag, String message) { 15 | Log.w(tag, message); 16 | DataHelper.INSTANCE.appendLog("[w] " + tag + " " + message); 17 | } 18 | 19 | public static void e(String tag, String message) { 20 | Log.e(tag, message); 21 | DataHelper.INSTANCE.appendLog("[e] " + tag + " " + message); 22 | } 23 | 24 | public static void e(String tag, String message, Exception e) { 25 | StringWriter writer = new StringWriter(); 26 | PrintWriter printWriter = new PrintWriter(writer); 27 | e.printStackTrace(printWriter); 28 | Throwable cause = e.getCause(); 29 | while (cause != null) { 30 | cause.printStackTrace(printWriter); 31 | cause = cause.getCause(); 32 | } 33 | printWriter.close(); 34 | String result = writer.toString(); 35 | 36 | Log.e(tag, result); 37 | DataHelper.INSTANCE.appendLog("[e] " + tag + " " + message + " " + result); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /freeform-server/src/main/java/io/sunshine0523/freeform/util/Settings.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform.util 2 | 3 | data class Settings( 4 | var enableSideBar: Boolean = false, 5 | var showImeInFreeform: Boolean = false, 6 | var notification: Boolean = false 7 | ) -------------------------------------------------------------------------------- /freeform-server/src/test/java/io/sunshine0523/freeform/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | 18 | 19 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.1.1" 3 | kotlin = "1.8.10" 4 | zygote-loader = "3.0" 5 | core-ktx = "1.10.1" 6 | ksp = "1.8.10-1.0.9" 7 | junit = "4.13.2" 8 | androidx-test-ext-junit = "1.1.5" 9 | espresso-core = "3.5.1" 10 | lifecycle-runtime-ktx = "2.6.1" 11 | lifecycle-viewmodel-ktx = "2.1.0" 12 | activity-compose = "1.7.0" 13 | compose-bom = "2023.03.00" 14 | androidx-appcompat-appcompat = "1.6.1" 15 | constraintlayout = "2.1.4" 16 | com-google-android-material-material = "1.9.0" 17 | hidden-api-bypass = "4.3" 18 | magic-library = "1.5" 19 | hidden-api-refine = "4.3.0" 20 | foundation = "1.4.0" 21 | systemuicontroller = "0.33.0-alpha" 22 | gson = "2.10.1" 23 | room = "2.5.1" 24 | glide = "4.16.0" 25 | drawablepainter = "0.32.0" 26 | compose-livedata = "1.4.3" 27 | nativehelper = "1.0.0" 28 | cxx = "1.2.0" 29 | navigation-fragment-ktx = "2.5.3" 30 | navigation-ui-ktx = "2.5.3" 31 | rikka-appcompat = "1.2.0-rc01" 32 | rikka-material = "1.6.6" 33 | 34 | [libraries] 35 | core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } 36 | lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } 37 | lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle-viewmodel-ktx" } 38 | activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } 39 | appcompat = { group = "dev.rikka.rikkax.appcompat", name = "appcompat", version.ref = "rikka-appcompat" } 40 | 41 | constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 42 | 43 | junit = { group = "junit", name = "junit", version.ref = "junit" } 44 | androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } 45 | espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } 46 | 47 | compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } 48 | ui = { group = "androidx.compose.ui", name = "ui" } 49 | ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } 50 | ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } 51 | ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } 52 | ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } 53 | ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } 54 | compose-material3 = { group = "androidx.compose.material3", name = "material3" } 55 | compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation"} 56 | compose-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "compose-livedata" } 57 | 58 | room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room"} 59 | room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room"} 60 | room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room"} 61 | 62 | material = { group = "dev.rikka.rikkax.material", name = "material", version.ref = "rikka-material" } 63 | 64 | systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "systemuicontroller"} 65 | 66 | hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hidden-api-bypass" } 67 | 68 | magic-library = { group = "com.github.kr328.magic", name = "library", version.ref = "magic-library"} 69 | 70 | hiddenapirefineannotationprocessor = { group = "dev.rikka.tools.refine", name = "annotation-processor", version.ref = "hidden-api-refine"} 71 | hiddenapirefineannotation = { group = "dev.rikka.tools.refine", name = "annotation", version.ref = "hidden-api-refine"} 72 | hiddenapirefineruntime = { group = "dev.rikka.tools.refine", name = "runtime", version.ref = "hidden-api-refine"} 73 | 74 | gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } 75 | 76 | glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } 77 | 78 | drawablepainter = { group = "com.google.accompanist", name = "accompanist-drawablepainter", version.ref = "drawablepainter" } 79 | 80 | nativehelper = { group = "dev.rikka.ndk.thirdparty", name = "nativehelper", version.ref = "nativehelper" } 81 | cxx = { group = "dev.rikka.ndk.thirdparty", name = "cxx", version.ref = "cxx" } 82 | [plugins] 83 | androidApplication = { id = "com.android.application", version.ref = "agp" } 84 | androidLibrary = { id = "com.android.library", version.ref = "agp" } 85 | kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 86 | hiddenApiRefine = { id = "dev.rikka.tools.refine", version.ref = "hidden-api-refine" } 87 | zygoteLoader = { id = "com.github.kr328.gradle.zygote", version.ref = "zygote-loader"} 88 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 89 | 90 | [bundles] 91 | 92 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 28 17:33:47 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /hidden-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /hidden-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.androidLibrary) 3 | } 4 | 5 | android { 6 | namespace = "io.sunshine0523.hidden_api" 7 | } 8 | 9 | dependencies { 10 | annotationProcessor(libs.hiddenapirefineannotationprocessor) 11 | compileOnly(libs.hiddenapirefineannotation) 12 | } -------------------------------------------------------------------------------- /hidden-api/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/hidden-api/consumer-rules.pro -------------------------------------------------------------------------------- /hidden-api/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /hidden-api/src/androidTest/java/io/sunshine0523/hidden/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.hidden 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("io.sunshine0523.hidden.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /hidden-api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/annotation/NonNull.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 | package android.annotation; 17 | 18 | import static java.lang.annotation.ElementType.FIELD; 19 | import static java.lang.annotation.ElementType.METHOD; 20 | import static java.lang.annotation.ElementType.PARAMETER; 21 | import static java.lang.annotation.RetentionPolicy.SOURCE; 22 | 23 | import java.lang.annotation.Retention; 24 | import java.lang.annotation.Target; 25 | 26 | /** 27 | * Denotes that a parameter, field or method return value can never be null. 28 | *

29 | * This is a marker annotation and it has no specific attributes. 30 | * 31 | * @paramDoc This value must never be {@code null}. 32 | * @returnDoc This value will never be {@code null}. 33 | * @hide 34 | */ 35 | @Retention(SOURCE) 36 | @Target({METHOD, PARAMETER, FIELD}) 37 | public @interface NonNull { 38 | } 39 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/annotation/Nullable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 | package android.annotation; 17 | 18 | import static java.lang.annotation.ElementType.FIELD; 19 | import static java.lang.annotation.ElementType.METHOD; 20 | import static java.lang.annotation.ElementType.PARAMETER; 21 | import static java.lang.annotation.RetentionPolicy.SOURCE; 22 | 23 | import java.lang.annotation.Retention; 24 | import java.lang.annotation.Target; 25 | 26 | /** 27 | * Denotes that a parameter, field or method return value can be null. 28 | *

29 | * When decorating a method call parameter, this denotes that the parameter can 30 | * legitimately be null and the method will gracefully deal with it. Typically 31 | * used on optional parameters. 32 | *

33 | * When decorating a method, this denotes the method might legitimately return 34 | * null. 35 | *

36 | * This is a marker annotation and it has no specific attributes. 37 | * 38 | * @paramDoc This value may be {@code null}. 39 | * @returnDoc This value may be {@code null}. 40 | */ 41 | @Retention(SOURCE) 42 | @Target({METHOD, PARAMETER, FIELD}) 43 | public @interface Nullable { 44 | } 45 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ActivityManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(ActivityManager.class) 6 | public class ActivityManagerHidden { 7 | public static class TaskSnapshot { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ActivityOptionsHidden.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Bundle; 4 | 5 | import dev.rikka.tools.refine.RefineAs; 6 | 7 | @RefineAs(ActivityOptions.class) 8 | public class ActivityOptionsHidden { 9 | public ActivityOptions setCallerDisplayId(int callerDisplayId) { 10 | throw new RuntimeException("Stub!"); 11 | } 12 | 13 | public Bundle toBundle() { 14 | throw new RuntimeException("Stub!"); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public class ActivityThread { 4 | public static ActivityThread currentActivityThread() { 5 | throw new RuntimeException("Stub!"); 6 | } 7 | 8 | public Application getApplication() { 9 | throw new RuntimeException("Stub!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/IActivityManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.IIntentReceiver; 4 | import android.content.IIntentSender; 5 | import android.content.Intent; 6 | import android.os.Binder; 7 | import android.os.Bundle; 8 | import android.os.IBinder; 9 | import android.os.IInterface; 10 | 11 | /** 12 | * Only For Android 8.1-Android 9 13 | */ 14 | public interface IActivityManager extends IInterface { 15 | void registerTaskStackListener(ITaskStackListener listener) throws RuntimeException; 16 | void unregisterTaskStackListener(ITaskStackListener listener) throws RuntimeException; 17 | boolean removeTask(int taskId) throws RuntimeException; 18 | void moveStackToDisplay(int stackId, int displayId) throws RuntimeException; 19 | 20 | int sendIntentSender(IIntentSender target, IBinder whitelistToken, int code, 21 | Intent intent, String resolvedType, IIntentReceiver finishedReceiver, 22 | String requiredPermission, Bundle options); 23 | 24 | abstract class Stub extends Binder implements IActivityManager { 25 | public static IActivityManager asInterface(IBinder binder) { 26 | throw new RuntimeException("Stub!"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/IActivityTaskManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | public interface IActivityTaskManager extends IInterface { 8 | void registerTaskStackListener(ITaskStackListener listener) throws RuntimeException; 9 | void unregisterTaskStackListener(ITaskStackListener listener) throws RuntimeException; 10 | boolean removeTask(int taskId); 11 | //For A11-A13 12 | void moveRootTaskToDisplay(int taskId, int displayId) throws RuntimeException; 13 | //Only for A10 14 | void moveStackToDisplay(int stackId, int displayId) throws RuntimeException; 15 | 16 | abstract class Stub extends Binder implements IActivityTaskManager { 17 | public static IActivityTaskManager asInterface(IBinder binder) { 18 | throw new RuntimeException("Stub!"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/IAppTask.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface IAppTask extends IInterface { 6 | } 7 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/INotificationManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.ComponentName; 4 | import android.os.Binder; 5 | import android.os.IBinder; 6 | import android.os.IInterface; 7 | import android.service.notification.INotificationListener; 8 | 9 | public interface INotificationManager extends IInterface { 10 | void registerListener(INotificationListener listener, ComponentName component, int userid); 11 | void unregisterListener(INotificationListener listener, int userid); 12 | void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id, int userId); 13 | void cancelNotificationsFromListener(INotificationListener token, String[] keys); 14 | abstract class Stub extends Binder implements INotificationManager { 15 | public static INotificationManager asInterface(IBinder binder) { 16 | throw new RuntimeException("Stub!"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/PendingIntentHidden.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.IIntentSender; 4 | import android.os.IBinder; 5 | 6 | import dev.rikka.tools.refine.RefineAs; 7 | 8 | @RefineAs(PendingIntent.class) 9 | public class PendingIntentHidden { 10 | public IIntentSender getTarget() { 11 | throw new RuntimeException("Stub!"); 12 | } 13 | 14 | public IBinder getWhitelistToken() { 15 | throw new RuntimeException("Stub!"); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/content/IIntentReceiver.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface IIntentReceiver extends IInterface { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/content/IIntentSender.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface IIntentSender extends IInterface { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/Parcel.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class Parcel { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/Parcelable.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public interface Parcelable { 4 | interface Creator{ 5 | public T createFromParcel(Parcel source); 6 | public T[] newArray(int size); 7 | } 8 | void writeToParcel(Parcel dest, int flags); 9 | int describeContents(); 10 | } 11 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/SELinux.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class SELinux { 4 | public static boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm) { 5 | throw new UnsupportedOperationException("Stub"); 6 | } 7 | 8 | public static boolean setFileContext(String path, String context) { 9 | throw new UnsupportedOperationException("Stub"); 10 | } 11 | 12 | public static boolean setFSCreateContext(String context){ 13 | throw new UnsupportedOperationException("Stub"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/ServiceManager.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import java.util.Map; 4 | 5 | public class ServiceManager { 6 | 7 | /** 8 | * Returns a reference to a service with the given name. 9 | * 10 | * @param name the name of the service to get 11 | * @return a reference to the service, or null if the service doesn't exist 12 | */ 13 | public static IBinder getService(String name) { 14 | throw new RuntimeException("Stub!"); 15 | } 16 | 17 | /** 18 | * Place a new @a service called @a name into the service 19 | * manager. 20 | * 21 | * @param name the name of the new service 22 | * @param service the service object 23 | */ 24 | public static void addService(String name, IBinder service) { 25 | throw new RuntimeException("Stub!"); 26 | } 27 | 28 | /** 29 | * This is only intended to be called when the process is first being brought 30 | * up and bound by the activity manager. There is only one thread in the process 31 | * at that time, so no locking is done. 32 | * 33 | * @param cache the cache of service references 34 | * @hide 35 | */ 36 | public static void initServiceCache(Map cache) { 37 | throw new RuntimeException("Stub!"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/UserHandleHidden.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(UserHandle.class) 6 | public class UserHandleHidden { 7 | 8 | public UserHandleHidden(int userId) { 9 | throw new RuntimeException("Stub!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/service/notification/INotificationListener.java: -------------------------------------------------------------------------------- 1 | package android.service.notification; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationChannelGroup; 6 | import android.os.Binder; 7 | import android.os.Bundle; 8 | import android.os.IBinder; 9 | import android.os.IInterface; 10 | import android.os.UserHandle; 11 | 12 | import java.util.List; 13 | 14 | public interface INotificationListener extends IInterface { 15 | // listeners and assistant 16 | void onListenerConnected(NotificationRankingUpdate update); 17 | void onNotificationPosted(IStatusBarNotificationHolder notificationHolder, 18 | NotificationRankingUpdate update); 19 | void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons); 20 | // stats only for assistant 21 | void onNotificationRemoved(IStatusBarNotificationHolder notificationHolder, 22 | NotificationRankingUpdate update, NotificationStats stats, int reason); 23 | void onNotificationRankingUpdate(NotificationRankingUpdate update); 24 | void onListenerHintsChanged(int hints); 25 | void onInterruptionFilterChanged(int interruptionFilter); 26 | 27 | // companion device managers and assistants only 28 | void onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, int modificationType); 29 | void onNotificationChannelGroupModification(String pkgName, UserHandle user, NotificationChannelGroup group, int modificationType); 30 | 31 | // assistants only 32 | void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder notificationHolder, NotificationChannel channel, NotificationRankingUpdate update); 33 | void onNotificationSnoozedUntilContext(IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); 34 | void onNotificationsSeen(List keys); 35 | void onPanelRevealed(int items); 36 | void onPanelHidden(); 37 | void onNotificationVisibilityChanged(String key, boolean isVisible); 38 | void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); 39 | void onNotificationDirectReply(String key); 40 | void onSuggestedReplySent(String key, CharSequence reply, int source); 41 | void onActionClicked(String key, Notification.Action action, int source); 42 | void onNotificationClicked(String key); 43 | void onAllowedAdjustmentsChanged(); 44 | void onNotificationFeedbackReceived(String key, NotificationRankingUpdate update, Bundle feedback); 45 | 46 | abstract class Stub extends Binder implements INotificationListener { 47 | @Override 48 | public IBinder asBinder() { 49 | throw new RuntimeException("Stub!"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/service/notification/IStatusBarNotificationHolder.java: -------------------------------------------------------------------------------- 1 | package android.service.notification; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface IStatusBarNotificationHolder extends IInterface { 6 | /** Fetch the held StatusBarNotification. This method should only be called once per Holder */ 7 | StatusBarNotification get(); 8 | } 9 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/service/notification/NotificationRankingUpdate.java: -------------------------------------------------------------------------------- 1 | package android.service.notification; 2 | 3 | public class NotificationRankingUpdate { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/service/notification/NotificationStats.java: -------------------------------------------------------------------------------- 1 | package android.service.notification; 2 | 3 | public class NotificationStats { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/DisplayHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import dev.rikka.tools.refine.RefineAs; 7 | 8 | @RefineAs(Display.class) 9 | public class DisplayHidden { 10 | public static final int TYPE_OVERLAY = 4; 11 | 12 | public static final int STATE_ON = 2; 13 | 14 | public static final class Mode implements Parcelable { 15 | public static final Mode[] EMPTY_ARRAY = new Mode[0]; 16 | private Mode(Parcel in) { 17 | 18 | } 19 | 20 | public static final Creator CREATOR = new Creator() { 21 | @Override 22 | public Mode createFromParcel(Parcel in) { 23 | return new Mode(in); 24 | } 25 | 26 | @Override 27 | public Mode[] newArray(int size) { 28 | return new Mode[size]; 29 | } 30 | }; 31 | 32 | @Override 33 | public void writeToParcel(Parcel dest, int flags) { 34 | 35 | } 36 | 37 | @Override 38 | public int describeContents() { 39 | return 0; 40 | } 41 | 42 | public int getPhysicalWidth() { 43 | throw new RuntimeException("Stub!"); 44 | } 45 | 46 | public int getPhysicalHeight() { 47 | throw new RuntimeException("Stub!"); 48 | } 49 | 50 | public int getModeId() { 51 | throw new RuntimeException("Stub!"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/DisplayShapeHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(DisplayShape.class) 6 | public class DisplayShapeHidden { 7 | 8 | public static DisplayShape createDefaultDisplayShape( 9 | int displayWidth, int displayHeight, boolean isScreenRound) { 10 | throw new RuntimeException("Stub!"); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/IRotationWatcher.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | public interface IRotationWatcher extends IInterface { 8 | void onRotationChanged(int rotation) throws RuntimeException; 9 | 10 | abstract class Stub extends Binder implements IRotationWatcher{ 11 | @Override 12 | public IBinder asBinder() { 13 | throw new RuntimeException("Stub!"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/IWindowManager.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | public interface IWindowManager extends IInterface { 8 | int watchRotation(IRotationWatcher watcher, int displayId); 9 | 10 | void removeRotationWatcher(IRotationWatcher watcher); 11 | 12 | //Only support A12+ 13 | void setDisplayImePolicy(int displayId, int imePolicy); 14 | 15 | void freezeDisplayRotation(int displayId, int rotation); 16 | 17 | abstract class Stub extends Binder implements IWindowManager { 18 | public static IWindowManager asInterface(IBinder binder) { 19 | throw new RuntimeException("Stub!"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/SurfaceControlHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.IBinder; 4 | 5 | import dev.rikka.tools.refine.RefineAs; 6 | 7 | @RefineAs(SurfaceControl.class) 8 | public final class SurfaceControlHidden { 9 | 10 | public static IBinder createDisplay(String name, boolean secure) { 11 | throw new RuntimeException("Stub!"); 12 | } 13 | public static void destroyDisplay(IBinder displayToken) { 14 | throw new RuntimeException("Stub!"); 15 | } 16 | public static class Transaction { 17 | public static void setDisplaySize(IBinder displayToken, int width, int height) { 18 | throw new RuntimeException("Stub!"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/WindowInsetsHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | public class WindowInsetsHidden { 4 | public static final class Type { 5 | static final int FIRST = 1 << 0; 6 | public static final int STATUS_BARS = FIRST; 7 | public static final int NAVIGATION_BARS = 1 << 1; 8 | public static final int CAPTION_BAR = 1 << 2; 9 | 10 | static final int IME = 1 << 3; 11 | 12 | static final int SYSTEM_GESTURES = 1 << 4; 13 | static final int MANDATORY_SYSTEM_GESTURES = 1 << 5; 14 | static final int TAPPABLE_ELEMENT = 1 << 6; 15 | 16 | static final int DISPLAY_CUTOUT = 1 << 7; 17 | 18 | static final int WINDOW_DECOR = 1 << 8; 19 | 20 | static final int GENERIC_OVERLAYS = 1 << 9; 21 | static final int LAST = GENERIC_OVERLAYS; 22 | static final int SIZE = 10; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/WindowManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(WindowManager.class) 6 | public interface WindowManagerHidden{ 7 | public static class LayoutParams extends ViewGroup.LayoutParams{ 8 | /** 9 | * Flag to indicate that the window is controlling how it fits window insets on its own. 10 | * So we don't need to adjust its attributes for fitting window insets. 11 | */ 12 | public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000; 13 | 14 | /** 15 | * Flag to request creation of a BLAST (Buffer as LayerState) Layer. 16 | * If not specified the client will receive a BufferQueue layer. 17 | */ 18 | public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000; 19 | 20 | /** In a multiuser system if this flag is set and the owner is a system process then this 21 | * window will appear on all user screens. This overrides the default behavior of window 22 | * types that normally only appear on the owning user's screen. Refer to each window type 23 | * to determine its default behavior. 24 | */ 25 | public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010; 26 | 27 | /** 28 | * Indicates that this window is the rounded corners overlay present on some 29 | * devices this means that it will be excluded from: screenshots, 30 | * screen magnification, and mirroring. 31 | */ 32 | public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; 33 | 34 | /** 35 | * Flag to indicate that the window is a trusted overlay. 36 | */ 37 | public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000; 38 | 39 | private int mFitInsetsTypes; 40 | 41 | public int x; 42 | public int y; 43 | public int type; 44 | public int width; 45 | public int height; 46 | public int flags; 47 | public int format; 48 | public int privateFlags; 49 | 50 | public LayoutParams() { 51 | super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 52 | throw new RuntimeException("Stub!"); 53 | } 54 | 55 | public void setFitInsetsTypes(int types) { 56 | throw new RuntimeException("Stub!"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/view/WindowManagerPolicyConstants.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | public interface WindowManagerPolicyConstants { 4 | interface PointerEventListener { 5 | /** 6 | * 1. onPointerEvent will be called on the service.UiThread. 7 | * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a 8 | * copy() must be made and the copy must be recycled. 9 | **/ 10 | void onPointerEvent(MotionEvent motionEvent); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/window/TaskSnapshot.java: -------------------------------------------------------------------------------- 1 | package android.window; 2 | 3 | public class TaskSnapshot { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/internal/content/ReferrerIntent.java: -------------------------------------------------------------------------------- 1 | package com.android.internal.content; 2 | 3 | import android.content.Intent; 4 | 5 | public class ReferrerIntent extends Intent { 6 | } 7 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/internal/statusbar/IStatusBarService.java: -------------------------------------------------------------------------------- 1 | package com.android.internal.statusbar; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | public interface IStatusBarService extends IInterface { 8 | void collapsePanels(); 9 | 10 | abstract class Stub extends Binder implements IStatusBarService { 11 | public static IStatusBarService asInterface(IBinder binder) { 12 | throw new RuntimeException("Stub!"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/SystemServer.java: -------------------------------------------------------------------------------- 1 | package com.android.server; 2 | 3 | public class SystemServer { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/SystemService.java: -------------------------------------------------------------------------------- 1 | package com.android.server; 2 | 3 | public abstract class SystemService { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.view.DisplayHidden; 6 | 7 | import java.io.PrintWriter; 8 | 9 | abstract class DisplayAdapter { 10 | 11 | public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; 12 | public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; 13 | public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; 14 | 15 | public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, 16 | Context context, Handler handler, Listener listener, String name) { 17 | throw new RuntimeException("Stub!"); 18 | } 19 | 20 | public static DisplayHidden.Mode createMode(int width, int height, float refreshRate) { 21 | throw new RuntimeException("Stub!"); 22 | } 23 | 24 | public static DisplayHidden.Mode createMode(int width, int height, float refreshRate, 25 | float[] alternativeRefreshRates) { 26 | throw new RuntimeException("Stub!"); 27 | } 28 | 29 | public void dumpLocked(PrintWriter pw) { 30 | throw new RuntimeException("Stub!"); 31 | } 32 | 33 | public final Context getContext() { 34 | throw new RuntimeException("Stub!"); 35 | } 36 | 37 | public final Handler getHandler() { 38 | throw new RuntimeException("Stub!"); 39 | } 40 | 41 | public final String getName() { 42 | throw new RuntimeException("Stub!"); 43 | } 44 | 45 | public final DisplayManagerService.SyncRoot getSyncRoot() { 46 | throw new RuntimeException("Stub!"); 47 | } 48 | 49 | public void registerLocked() { 50 | throw new RuntimeException("Stub!"); 51 | } 52 | 53 | public final void sendDisplayDeviceEventLocked(final DisplayDevice device, final int event) { 54 | throw new RuntimeException("Stub!"); 55 | } 56 | 57 | public final void sendTraversalRequestLocked() { 58 | throw new RuntimeException("Stub!"); 59 | } 60 | 61 | public interface Listener { 62 | void onDisplayDeviceEvent(DisplayDevice device, int event); 63 | 64 | void onTraversalRequested(); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayControl.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.os.IBinder; 4 | 5 | public class DisplayControl { 6 | 7 | /** 8 | * Create a display in SurfaceFlinger. 9 | * 10 | * @param name The name of the display 11 | * @param secure Whether this display is secure. 12 | * @return The token reference for the display in SurfaceFlinger. 13 | */ 14 | public static IBinder createDisplay(String name, boolean secure) { 15 | throw new RuntimeException("Stub!"); 16 | } 17 | 18 | /** 19 | * Create a display in SurfaceFlinger. 20 | * 21 | * @param name The name of the display 22 | * @param secure Whether this display is secure. 23 | * @param requestedRefreshRate The requested refresh rate in frames per second. 24 | * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on 25 | * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded 26 | * up or down to a divisor of the physical display. If 0 is specified, the virtual 27 | * display is refreshed at the physical display refresh rate. 28 | * @return The token reference for the display in SurfaceFlinger. 29 | */ 30 | public static IBinder createDisplay(String name, boolean secure, 31 | float requestedRefreshRate) { 32 | throw new RuntimeException("Stub!"); 33 | } 34 | 35 | /** 36 | * Destroy a display in SurfaceFlinger. 37 | * 38 | * @param displayToken The display token for the display to be destroyed. 39 | */ 40 | public static void destroyDisplay(IBinder displayToken) { 41 | throw new RuntimeException("Stub!"); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 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.android.server.display; 18 | 19 | import android.content.Context; 20 | import android.os.IBinder; 21 | import android.view.Surface; 22 | import android.view.SurfaceControlHidden; 23 | 24 | /** 25 | * Represents a physical display device such as the built-in display 26 | * an external monitor, or a WiFi display. 27 | *

28 | * Display devices are guarded by the {@link DisplayManagerService.SyncRoot} lock. 29 | *

30 | */ 31 | abstract class DisplayDevice { 32 | 33 | public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId, 34 | Context context) { 35 | throw new RuntimeException("Stub!"); 36 | } 37 | 38 | public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) { 39 | throw new RuntimeException("Stub!"); 40 | } 41 | 42 | /** 43 | * Gets the Surface Flinger display token for this display. 44 | * 45 | * @return The display token, or null if the display is not being managed 46 | * by Surface Flinger. 47 | */ 48 | public final IBinder getDisplayTokenLocked() { 49 | throw new RuntimeException("Stub!"); 50 | } 51 | 52 | /** 53 | * Returns the unique id of the display device. 54 | */ 55 | public final String getUniqueId() { 56 | throw new RuntimeException("Stub!"); 57 | } 58 | 59 | /** 60 | * Returns whether the unique id of the device is stable across reboots. 61 | */ 62 | public abstract boolean hasStableUniqueId(); 63 | 64 | /** 65 | * Gets information about the display device. 66 | * 67 | * The information returned should not change between calls unless the display 68 | * adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and 69 | * {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply 70 | * the pending changes. 71 | * 72 | * @return The display device info, which should be treated as immutable by the caller. 73 | * The display device should allocate a new display device info object whenever 74 | * the data changes. 75 | */ 76 | public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked(); 77 | 78 | /** 79 | * Gives the display device a chance to update its properties while in a transaction. 80 | */ 81 | public void performTraversalLocked(SurfaceControlHidden.Transaction t) { 82 | throw new RuntimeException("Stub!"); 83 | } 84 | 85 | /** 86 | * Sets the display surface while in a transaction. 87 | */ 88 | public final void setSurfaceLocked(SurfaceControlHidden.Transaction t, Surface surface) { 89 | throw new RuntimeException("Stub!"); 90 | } 91 | } -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayDeviceInfo.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.view.DisplayHidden; 4 | import android.view.DisplayShape; 5 | 6 | final class DisplayDeviceInfo { 7 | /** 8 | * Flag: Indicates that this display device has secure video output, such as HDCP. 9 | */ 10 | public static final int FLAG_SECURE = 1 << 2; 11 | 12 | /** 13 | * Flag: Indicates that the display is suitable for presentations. 14 | */ 15 | public static final int FLAG_PRESENTATION = 1 << 6; 16 | 17 | /** 18 | * Flag: Only show this display's own content; do not mirror 19 | * the content of another display. 20 | */ 21 | public static final int FLAG_OWN_CONTENT_ONLY = 1 << 7; 22 | 23 | /** 24 | * Flag: This flag identifies secondary displays that should show system decorations, such as 25 | * status bar, navigation bar, home activity or IME. 26 | *

Note that this flag doesn't work without {@link #FLAG_TRUSTED}

27 | * @hide 28 | */ 29 | // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors 30 | public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12; 31 | 32 | /** 33 | * Flag: The display is trusted to show system decorations and receive inputs without users' 34 | * touch. 35 | * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS 36 | */ 37 | public static final int FLAG_TRUSTED = 1 << 13; 38 | 39 | /** 40 | * Touch attachment: Touch input is via the internal interface. 41 | */ 42 | public static final int TOUCH_INTERNAL = 1; 43 | 44 | /** 45 | * Touch attachment: Touch input is via an input device matching {@link VirtualDisplay}'s 46 | * uniqueId. 47 | * @hide 48 | */ 49 | public static final int TOUCH_VIRTUAL = 3; 50 | 51 | /** 52 | * Gets the name of the display device, which may be derived from EDID or 53 | * other sources. The name may be localized and displayed to the user. 54 | */ 55 | public String name; 56 | 57 | /** 58 | * Unique Id of display device. 59 | */ 60 | public String uniqueId; 61 | 62 | /** 63 | * The width of the display in its natural orientation, in pixels. 64 | * This value is not affected by display rotation. 65 | */ 66 | public int width; 67 | 68 | /** 69 | * The height of the display in its natural orientation, in pixels. 70 | * This value is not affected by display rotation. 71 | */ 72 | public int height; 73 | 74 | /** 75 | * The active mode of the display. 76 | */ 77 | public int modeId; 78 | 79 | /** 80 | * The default mode of the display. 81 | */ 82 | public int defaultModeId; 83 | 84 | /** 85 | * The supported modes of the display. 86 | */ 87 | public DisplayHidden.Mode[] supportedModes = DisplayHidden.Mode.EMPTY_ARRAY; 88 | 89 | 90 | /** 91 | * The nominal apparent density of the display in DPI used for layout calculations. 92 | * This density is sensitive to the viewing distance. A big TV and a tablet may have 93 | * the same apparent density even though the pixels on the TV are much bigger than 94 | * those on the tablet. 95 | */ 96 | public int densityDpi; 97 | 98 | /** 99 | * The physical density of the display in DPI in the X direction. 100 | * This density should specify the physical size of each pixel. 101 | */ 102 | public float xDpi; 103 | 104 | /** 105 | * The physical density of the display in DPI in the X direction. 106 | * This density should specify the physical size of each pixel. 107 | */ 108 | public float yDpi; 109 | 110 | /** 111 | * This is how far in advance a buffer must be queued for presentation at 112 | * a given time. If you want a buffer to appear on the screen at 113 | * time N, you must submit the buffer before (N - bufferDeadlineNanos). 114 | */ 115 | public long presentationDeadlineNanos; 116 | 117 | /** 118 | * Display flags. 119 | */ 120 | public int flags; 121 | 122 | /** 123 | * The {@link RoundedCorners} if present or {@code null} otherwise. 124 | */ 125 | public DisplayShape displayShape; 126 | 127 | /** 128 | * The touch attachment, per {@link DisplayViewport#touch}. 129 | */ 130 | public int touch; 131 | 132 | /** 133 | * Display type. 134 | */ 135 | public int type; 136 | 137 | /** 138 | * Display state. 139 | */ 140 | public int state = DisplayHidden.STATE_ON; 141 | } 142 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayDeviceRepository.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | class DisplayDeviceRepository implements DisplayAdapter.Listener{ 4 | public void addListener(Listener listener) { 5 | throw new RuntimeException("Stub!"); 6 | } 7 | 8 | @Override 9 | public void onDisplayDeviceEvent(DisplayDevice device, int event) { 10 | throw new RuntimeException("Stub!"); 11 | } 12 | 13 | @Override 14 | public void onTraversalRequested() { 15 | throw new RuntimeException("Stub!"); 16 | } 17 | 18 | /** 19 | * Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}. 20 | */ 21 | public interface Listener { 22 | void onDisplayDeviceEventLocked(DisplayDevice device, int event); 23 | 24 | // TODO: multi-display - Try to remove the need for requestTraversal...it feels like 25 | // a shoe-horned method for a shoe-horned feature. 26 | void onTraversalRequested(); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayManagerService.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | 6 | import com.android.server.SystemService; 7 | 8 | public final class DisplayManagerService extends SystemService { 9 | 10 | private final SyncRoot mSyncRoot = null; 11 | private final Context mContext = null; 12 | private final DisplayManagerHandler mHandler = null; 13 | private final DisplayDeviceRepository mDisplayDeviceRepo = null; 14 | private final Handler mUiHandler = null; 15 | 16 | public static final class SyncRoot { 17 | public SyncRoot() { } 18 | } 19 | 20 | private final class DisplayManagerHandler { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/DisplayModeDirector.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | public class DisplayModeDirector { 4 | public static final class DesiredDisplayModeSpecs { 5 | public int baseModeId; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/LogicalDisplay.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | final class LogicalDisplay { 4 | /** 5 | * Gets the logical display id of this logical display. 6 | * 7 | * @return The logical display id. 8 | */ 9 | public int getDisplayIdLocked() { 10 | throw new RuntimeException("Stub!"); 11 | } 12 | 13 | public DisplayDevice getPrimaryDisplayDeviceLocked() { 14 | throw new RuntimeException("Stub!"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/display/LogicalDisplayMapper.java: -------------------------------------------------------------------------------- 1 | package com.android.server.display; 2 | 3 | class LogicalDisplayMapper { 4 | public LogicalDisplay getDisplayLocked(DisplayDevice device) { 5 | throw new RuntimeException("Stub!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/input/InputManagerService.java: -------------------------------------------------------------------------------- 1 | package com.android.server.input; 2 | 3 | import android.view.InputEvent; 4 | 5 | public class InputManagerService { 6 | public boolean injectInputEvent(InputEvent event, int mode) { 7 | throw new RuntimeException("Stub!"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/com/android/server/wm/WindowManagerService.java: -------------------------------------------------------------------------------- 1 | package com.android.server.wm; 2 | 3 | public class WindowManagerService { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/test/java/io/sunshine0523/hidden/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.hidden 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /service/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.androidLibrary) 4 | } 5 | 6 | android { 7 | namespace = "com.sunshine.freeform" 8 | 9 | buildFeatures { 10 | aidl = true 11 | } 12 | 13 | buildTypes { 14 | release { 15 | isMinifyEnabled = false 16 | proguardFiles( 17 | getDefaultProguardFile("proguard-android-optimize.txt"), 18 | "proguard-rules.pro" 19 | ) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshine0523/Mi-Freeform/0a078b385fbd12d4bfd83dbe905e8eaa62ac36c1/service/consumer-rules.pro -------------------------------------------------------------------------------- /service/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class * implements android.os.IInterface {*;} -------------------------------------------------------------------------------- /service/src/androidTest/java/io/sunshine0523/freeform/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("io.sunshine0523.service.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /service/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /service/src/main/aidl/io/sunshine0523/freeform/IMiFreeformDisplayCallback.aidl: -------------------------------------------------------------------------------- 1 | // IMiFreeformDisplayCallback.aidl 2 | package io.sunshine0523.freeform; 3 | 4 | oneway interface IMiFreeformDisplayCallback { 5 | void onDisplayPaused(); 6 | void onDisplayResumed(); 7 | void onDisplayStopped(); 8 | void onDisplayAdd(int displayId); 9 | } -------------------------------------------------------------------------------- /service/src/main/aidl/io/sunshine0523/freeform/IMiFreeformUIService.aidl: -------------------------------------------------------------------------------- 1 | package io.sunshine0523.freeform; 2 | 3 | import android.content.ComponentName; 4 | import android.view.InputEvent; 5 | import android.view.Surface; 6 | import android.os.IBinder; 7 | import io.sunshine0523.freeform.IMiFreeformDisplayCallback; 8 | 9 | interface IMiFreeformUIService { 10 | // start freeform in system 11 | void startAppInFreeform(String packageName, String activityName, int userId, in PendingIntent pendingIntent, 12 | int width, int height, int densityDpi, float refreshRate, 13 | boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, 14 | String resPkg, String layoutName) = 0; 15 | // remove freeform by freeformId: packageName,activityName,userId 16 | void removeFreeform(String freeformId) = 1; 17 | // create freeform in user 18 | void createFreeformInUser(String name, int width, int height, int densityDpi, float refreshRate, 19 | boolean secure, boolean ownContentOnly, boolean shouldShowSystemDecorations, 20 | in Surface surface, IMiFreeformDisplayCallback callback) = 2; 21 | void resizeFreeform(IBinder appToken, int width, int height, int densityDpi) = 3; 22 | void releaseFreeform(IBinder appToken) = 4; 23 | boolean ping() = 5; 24 | String getSettings() = 6; 25 | void setSettings(String settings) = 7; 26 | String getLog() = 8; 27 | void clearLog() = 9; 28 | void collapseStatusBar() = 10; 29 | void cancelNotification(String key) = 11; 30 | } -------------------------------------------------------------------------------- /service/src/test/java/io/sunshine0523/freeform/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | //enable projects.x for implementation 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | gradlePluginPortal() 7 | google() 8 | mavenCentral() 9 | maven(url = "https://maven.kr328.app/releases") 10 | } 11 | } 12 | dependencyResolutionManagement { 13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 14 | repositories { 15 | google() 16 | mavenCentral() 17 | maven(url = "https://maven.kr328.app/releases") 18 | } 19 | } 20 | 21 | rootProject.name = "Mi-Freeform" 22 | include( 23 | ":app", 24 | ":hidden-api", 25 | ":freeform-server", 26 | ":service" 27 | ) 28 | --------------------------------------------------------------------------------