├── .gitignore ├── .idea ├── deploymentTargetSelector.xml └── inspectionProfiles │ └── Project_Default.xml ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── abeja │ │ └── gamecenterreplacer │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── abeja │ │ │ └── gamecenterreplacer │ │ │ ├── BackgroundService.kt │ │ │ ├── MainActivity.kt │ │ │ ├── UI.kt │ │ │ ├── ViewModel.kt │ │ │ ├── data.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── discord_mark.xml │ │ ├── github_mark.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── abeja │ └── gamecenterreplacer │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── app_icon_transparent.png ├── screenshot_01.jpg └── screenshot_02.jpg └── 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 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Game Space Replacer 2 | 3 |
4 | 5 |
6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 | Take control of the red switch on your REDMAGIC Android device! 14 |
15 | 16 | ## Description 17 | 18 | **Game Space Replacer** is an Android app that allows you to customize the functionality of the red switch on your REDMAGIC device. 19 | 20 | With this app, you can replace the default behavior of the red switch to launch your favorite game or app automatically when triggered. 21 | 22 | ## Features 23 | 24 | - **Custom app selector**: Choose which app should launch when the red switch is pressed. 25 | - **Minimal UI**: Clean, simple interface to manage the red switch functionality. 26 | - **Privacy friendly**: Open source and no internet access, we don't store your data! 27 | 28 | ## Requirements 29 | 30 | - Any Global modern REDMAGIC device running REDMAGIC OS. 31 | - Necessary permissions: 32 | - Usage Stats Permission > Check if the Game Space has been launched. 33 | - Display over other apps permission > Launch a different app on top of the Game Space. 34 | 35 | ## Tested Devices 36 | 37 | The app has been tested on the following devices: 38 | 39 | - ✅ **REDMAGIC 7s Pro** 40 | - ✅ **REDMAGIC 8 Pro** 41 | - ✅ **REDMAGIC 10 Pro** 42 | 43 | It should work on any modern REDMAGIC device. If you encounter any issues, please feel free to open an issue on GitHub. **Note that the Chinese firmware isn't officially supported.** 44 | 45 | ## Common Issues 46 | - **REDMAGIC 10 Devices must have set the switch to launch the Game Space.** 47 | - The REDMAGIC Game Space briefly appears before the selected app launches. 48 | 49 | ## Installation 50 | - Go to the [Releases page](https://github.com/TheRealCrazyfuy/GameSpaceReplacer/releases) and download the latest release. 51 | - Once the download is finished open the `.apk` and install it. 52 | - Then just follow the instructions inside the app. 53 | - **On Android 15+ you have to manually give these permissions from settings. [Learn more](https://www.androidpolice.com/android-15-sideloading-restrictions-bad-users/)** 54 | 55 | ## Contributions 56 | All contributions are welcome, open a pull request and we'll look into it, thanks. 57 | 58 | ## Screenshots 59 | 60 | 61 | 62 | ## Community 63 | Join our Discord community to connect directly with the project, share your feedback, and get the latest updates. Whether you need support or want to contribute, we’d love to have you with us! 64 | 65 | [![Discord widget](https://discord.com/api/guilds/942133699523272704/widget.png?style=banner2)](https://discord.gg/Hc4UPXqc4j) 66 | 67 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.abeja.gamecenterreplacer" 8 | compileSdk = 35 9 | 10 | defaultConfig { 11 | applicationId = "com.abeja.gamecenterreplacer" 12 | minSdk = 30 13 | targetSdk = 35 14 | versionCode = 2 15 | versionName = "1.1" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary = true 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isMinifyEnabled = false 26 | proguardFiles( 27 | getDefaultProguardFile("proguard-android-optimize.txt"), 28 | "proguard-rules.pro" 29 | ) 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_1_8 34 | targetCompatibility = JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = "1.8" 38 | } 39 | buildFeatures { 40 | compose = true 41 | buildConfig = true 42 | } 43 | composeOptions { 44 | kotlinCompilerExtensionVersion = "1.5.1" 45 | } 46 | packaging { 47 | resources { 48 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 49 | } 50 | } 51 | lint { 52 | baseline = file("lint-baseline.xml") 53 | } 54 | } 55 | 56 | dependencies { 57 | 58 | implementation(libs.androidx.core.ktx) 59 | implementation(libs.androidx.lifecycle.runtime.ktx) 60 | implementation(libs.androidx.activity.ktx) 61 | implementation(libs.androidx.activity.compose) 62 | implementation(platform(libs.androidx.compose.bom)) 63 | implementation(libs.androidx.ui) 64 | implementation(libs.androidx.ui.graphics) 65 | implementation(libs.androidx.ui.tooling.preview) 66 | implementation(libs.androidx.material3) 67 | implementation(libs.androidx.runtime.livedata) 68 | testImplementation(libs.junit) 69 | androidTestImplementation(libs.androidx.junit) 70 | androidTestImplementation(libs.androidx.espresso.core) 71 | androidTestImplementation(platform(libs.androidx.compose.bom)) 72 | androidTestImplementation(libs.androidx.ui.test.junit4) 73 | debugImplementation(libs.androidx.ui.tooling) 74 | debugImplementation(libs.androidx.ui.test.manifest) 75 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/abeja/gamecenterreplacer/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 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.abeja.gamecenterreplacer", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/BackgroundService.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.Service 6 | import android.app.usage.UsageEvents 7 | import android.app.usage.UsageStatsManager 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.os.IBinder 11 | import android.provider.Settings 12 | import android.util.Log 13 | import androidx.core.app.NotificationCompat 14 | import kotlinx.coroutines.* 15 | import kotlinx.coroutines.flow.Flow 16 | import kotlinx.coroutines.flow.flow 17 | 18 | class BackgroundService : Service() { 19 | private val targetAppPackage = serviceData.appTargetPackage 20 | private val targetAppName = serviceData.appTargetName 21 | private val triggerAppPackage = "cn.nubia.gamelauncher" 22 | private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) 23 | 24 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 25 | //requestUsageStatsPermission() 26 | 27 | val channelId = "GameCenterReplacer" 28 | val channelName = "Background Service" 29 | val notificationChannel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT) 30 | val notificationManager = getSystemService(NotificationManager::class.java) 31 | notificationManager.createNotificationChannel(notificationChannel) 32 | 33 | val notification = NotificationCompat.Builder(this, channelId) 34 | .setContentTitle("Red switch set to: $targetAppName") 35 | //.setContentText("Long press this notification to hide.") 36 | .setSmallIcon(R.mipmap.ic_launcher) 37 | .build() 38 | 39 | startForeground(1, notification) 40 | 41 | serviceScope.launch { 42 | monitorApps().collect { event -> 43 | //Log.d("BackgroundService", "Event: ${event.packageName}") 44 | if ( event.packageName == triggerAppPackage) { 45 | //Log.d("BackgroundService", "Trigger app detected") 46 | //startOrResumeTargetApp() 47 | } 48 | } 49 | } 50 | 51 | return START_STICKY 52 | } 53 | 54 | private fun monitorApps(): Flow = flow { 55 | val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager 56 | while (true) { 57 | val endTime = System.currentTimeMillis() 58 | val beginTime = endTime - 5000 59 | val events = usageStatsManager.queryEvents(beginTime, endTime) 60 | val event = UsageEvents.Event() 61 | var currentForegroundApp: String? = null 62 | 63 | while (events.hasNextEvent()) { 64 | events.getNextEvent(event) 65 | if (event.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { 66 | currentForegroundApp = event.packageName 67 | } 68 | emit(event) 69 | } 70 | 71 | if (currentForegroundApp == triggerAppPackage) { 72 | Log.d("BackgroundService", "Trigger app is currently on screen") 73 | startOrResumeTargetApp() 74 | } 75 | 76 | delay(1000) // TODO: get rid of this delay while not tanking the battery life // 77 | } 78 | } 79 | 80 | private fun startOrResumeTargetApp() { 81 | val needKillGameLauncher = Settings.Global.getInt(contentResolver, "gcs_need_kill_game_launcher", 0) 82 | if (needKillGameLauncher == 0) { // check if the competitive key is on too 83 | Log.d("BackgroundService", "Attempting to start target app: $targetAppPackage") 84 | val launchIntent = targetAppPackage?.let { packageManager.getLaunchIntentForPackage(it) } 85 | if (launchIntent != null) { 86 | launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 87 | Log.d("BackgroundService", "Launching target app: $targetAppPackage") 88 | startActivity(launchIntent) 89 | } else { 90 | Log.e("BackgroundService", "Failed to find launch intent for package: $targetAppPackage") 91 | } 92 | } 93 | 94 | } 95 | 96 | override fun onBind(intent: Intent?): IBinder? = null 97 | 98 | override fun onDestroy() { 99 | serviceScope.cancel() 100 | super.onDestroy() 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.activity.viewModels 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.material3.ExperimentalMaterial3Api 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Scaffold 13 | import androidx.compose.material3.Text 14 | import androidx.compose.material3.TopAppBar 15 | import androidx.compose.material3.TopAppBarDefaults.topAppBarColors 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import com.abeja.gamecenterreplacer.ui.theme.GamecenterreplacerTheme 21 | 22 | class MainActivity : ComponentActivity() { 23 | private val viewModel: ViewModel by viewModels() 24 | 25 | @OptIn(ExperimentalMaterial3Api::class) 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | enableEdgeToEdge() 29 | viewModel.onLaunch(this) 30 | setContent { 31 | GamecenterreplacerTheme { 32 | Scaffold(modifier = Modifier.fillMaxSize(), 33 | topBar = { 34 | TopAppBar( 35 | colors = topAppBarColors( 36 | containerColor = MaterialTheme.colorScheme.primaryContainer, 37 | titleContentColor = MaterialTheme.colorScheme.primary, 38 | ), 39 | title = { 40 | Text(stringResource(R.string.app_name)) 41 | } 42 | ) 43 | } 44 | ) { innerPadding -> 45 | MainUI(modifier = Modifier.padding(innerPadding), viewModel) 46 | } 47 | } 48 | } 49 | } 50 | 51 | override fun onResume() { 52 | super.onResume() 53 | viewModel.onLaunch(this) 54 | } 55 | } 56 | 57 | @Composable 58 | fun Greeting(name: String, modifier: Modifier = Modifier) { 59 | Text( 60 | text = "Hello $name!", 61 | modifier = modifier 62 | ) 63 | } 64 | 65 | @Preview(showBackground = true) 66 | @Composable 67 | fun GreetingPreview() { 68 | GamecenterreplacerTheme { 69 | Greeting("Android") 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/UI.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 2 | 3 | import android.content.Context 4 | import android.content.pm.ResolveInfo 5 | import androidx.activity.compose.rememberLauncherForActivityResult 6 | import androidx.activity.result.ActivityResultLauncher 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.compose.foundation.Image 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.clickable 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Column 13 | import androidx.compose.foundation.layout.Row 14 | import androidx.compose.foundation.layout.Spacer 15 | import androidx.compose.foundation.layout.fillMaxWidth 16 | import androidx.compose.foundation.layout.padding 17 | import androidx.compose.foundation.layout.size 18 | import androidx.compose.foundation.layout.width 19 | import androidx.compose.foundation.lazy.LazyColumn 20 | import androidx.compose.foundation.lazy.items 21 | import androidx.compose.material.icons.Icons 22 | import androidx.compose.material.icons.automirrored.filled.List 23 | import androidx.compose.material.icons.filled.Warning 24 | import androidx.compose.material3.AlertDialog 25 | import androidx.compose.material3.Icon 26 | import androidx.compose.material3.MaterialTheme 27 | import androidx.compose.material3.Switch 28 | import androidx.compose.material3.Text 29 | import androidx.compose.material3.TextButton 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.livedata.observeAsState 33 | import androidx.compose.runtime.mutableStateOf 34 | import androidx.compose.runtime.remember 35 | import androidx.compose.ui.Alignment 36 | import androidx.compose.ui.Modifier 37 | import androidx.compose.ui.graphics.Color 38 | import androidx.compose.ui.graphics.asImageBitmap 39 | import androidx.compose.ui.graphics.vector.ImageVector 40 | import androidx.compose.ui.platform.LocalContext 41 | import androidx.compose.ui.res.painterResource 42 | import androidx.compose.ui.unit.dp 43 | import androidx.core.graphics.drawable.toBitmap 44 | import android.Manifest 45 | import android.util.Log 46 | 47 | @Composable 48 | fun MainUI(modifier: Modifier = Modifier, viewModel: ViewModel) { 49 | val context = LocalContext.current 50 | val mainSwitchStatus = remember { 51 | mutableStateOf( 52 | viewModel.checkifServiceisRunning( 53 | context, 54 | BackgroundService::class.java 55 | ) 56 | ) 57 | } 58 | val showAppsDialog = remember { mutableStateOf(false) } 59 | 60 | // TODO: Figure a better way to update the UI 61 | val isUsageStatsPermissionGranted by viewModel.isUsageStatsPermissionGranted.observeAsState(false) 62 | val isOnTopPermissionGranted by viewModel.isOnTopPermissionGranted.observeAsState(false) 63 | val isNotificationPermissionGranted by viewModel.isNotificationPermissionGranted.observeAsState(false) 64 | val appHasBeenChoosed by viewModel.appHasBeenChoosed.observeAsState(false) 65 | val appTargetName by viewModel.appTargetName.observeAsState("None") 66 | 67 | val requestNotificationLauncher: ActivityResultLauncher = rememberLauncherForActivityResult( 68 | contract = ActivityResultContracts.RequestPermission() 69 | ) { isGranted: Boolean -> 70 | if (isGranted) { 71 | // Permission granted 72 | Log.d("MainUI", "Notification permission granted") 73 | } else { 74 | // Permission denied 75 | Log.d("MainUI", "Notification permission denied") 76 | viewModel.openNotificationPermissionSettings(context) 77 | } 78 | } 79 | 80 | LazyColumn(modifier = modifier 81 | .padding(top = 56.dp) 82 | .fillMaxWidth()) { 83 | 84 | item { 85 | /** 86 | * Permissions check 87 | */ 88 | if (!isUsageStatsPermissionGranted) { 89 | StandardText("Usage access is required") 90 | StandardButton( 91 | "Allow usage access", 92 | Icons.Default.Warning 93 | ) { viewModel.requestUsageStatsPermission(context) } 94 | } 95 | if (!isOnTopPermissionGranted) { 96 | StandardText("On top permission is required") 97 | StandardButton( 98 | "Allow on top permission", 99 | Icons.Default.Warning 100 | ) { viewModel.requestOnTopPermission(context) } 101 | } 102 | if (!isNotificationPermissionGranted) { 103 | StandardText("Notification access is required") 104 | StandardButton( 105 | "Allow notification access", 106 | Icons.Default.Warning 107 | ) { requestNotificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } 108 | } 109 | } 110 | 111 | item { 112 | /** 113 | * Main UI 114 | */ 115 | StandardText("Turn on the switch below to automatically launch your chosen app whenever you activate the competitive key.") 116 | StandardSwitch( 117 | "Replace Game Space", 118 | mainSwitchStatus.value, 119 | enabled = isUsageStatsPermissionGranted && isOnTopPermissionGranted && isNotificationPermissionGranted && appHasBeenChoosed 120 | ) { 121 | mainSwitchStatus.value = it 122 | viewModel.setServiceStatus(context, mainSwitchStatus.value) 123 | } 124 | 125 | StandardText( 126 | "Chosen app: $appTargetName", 127 | ) 128 | StandardButton("Choose a different app", Icons.AutoMirrored.Filled.List) { 129 | showAppsDialog.value = true 130 | } 131 | } 132 | 133 | item { 134 | /** 135 | * App list dialog 136 | */ 137 | if (showAppsDialog.value) { 138 | AppListDialog(context, viewModel, { showAppsDialog.value = false }) { 139 | viewModel.setApptarget( 140 | it.activityInfo.packageName, 141 | it.loadLabel(context.packageManager).toString(), 142 | context 143 | ) 144 | showAppsDialog.value = false 145 | } 146 | } 147 | 148 | StandardText("Version ${BuildConfig.VERSION_NAME}") 149 | 150 | StandardLinkIcon( 151 | onClickGitHub = { viewModel.openGitHubRepository(context, "https://www.github.com/therealcrazyfuy/GameSpaceReplacer") }, 152 | onClickDiscord = { viewModel.openGitHubRepository(context, "https://discord.gg/Hc4UPXqc4j") } 153 | ) 154 | } 155 | } 156 | } 157 | 158 | @Composable 159 | fun StandardText(text: String) { 160 | Text( 161 | text = text, 162 | style = MaterialTheme.typography.bodyLarge, 163 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp) 164 | ) 165 | } 166 | 167 | @Composable 168 | fun StandardSwitch( 169 | text: String, 170 | checked: Boolean, 171 | enabled: Boolean = true, 172 | onCheckedChange: (Boolean) -> Unit 173 | ) { 174 | Row( 175 | modifier = Modifier 176 | .fillMaxWidth() 177 | .padding(horizontal = 16.dp, vertical = 12.dp) 178 | .clickable(enabled) { onCheckedChange(!checked) }, 179 | verticalAlignment = Alignment.CenterVertically 180 | ) { 181 | Text( 182 | text = text, 183 | style = MaterialTheme.typography.bodyLarge, 184 | modifier = Modifier.padding(end = 8.dp) 185 | ) 186 | Spacer(modifier = Modifier.weight(1f)) 187 | Switch( 188 | checked = checked, 189 | enabled = enabled, 190 | onCheckedChange = { 191 | onCheckedChange(it) 192 | } 193 | ) 194 | } 195 | } 196 | 197 | @Composable 198 | fun StandardButton( 199 | text: String, 200 | icon: ImageVector, 201 | modifier: Modifier = Modifier, 202 | onClick: () -> Unit 203 | ) { 204 | Row( 205 | modifier = Modifier 206 | .fillMaxWidth() 207 | .clickable(onClick = onClick) 208 | .padding(horizontal = 16.dp, vertical = 12.dp) 209 | .background(Color.Transparent), 210 | verticalAlignment = Alignment.CenterVertically 211 | ) { 212 | Icon( 213 | imageVector = icon, 214 | contentDescription = null, 215 | modifier = Modifier.size(24.dp) 216 | ) 217 | Spacer(modifier = Modifier.width(16.dp)) 218 | Text( 219 | text = text, 220 | style = MaterialTheme.typography.bodyLarge, 221 | ) 222 | } 223 | } 224 | 225 | @Composable 226 | fun StandardLinkIcon( 227 | onClickGitHub: () -> Unit, 228 | onClickDiscord: () -> Unit 229 | ) { 230 | Row( 231 | modifier = Modifier 232 | .fillMaxWidth() 233 | .padding(horizontal = 16.dp, vertical = 12.dp) 234 | .background(Color.Transparent), 235 | verticalAlignment = Alignment.CenterVertically, 236 | horizontalArrangement = Arrangement.Center 237 | ) { 238 | Icon( 239 | painter = painterResource(id= R.drawable.github_mark), 240 | contentDescription = null, 241 | modifier = Modifier.size(42.dp).padding(end = 8.dp).clickable(onClick = onClickGitHub) 242 | ) 243 | Icon( 244 | painter = painterResource(id= R.drawable.discord_mark), 245 | contentDescription = null, 246 | modifier = Modifier.size(42.dp).padding(end = 8.dp).clickable(onClick = onClickDiscord) 247 | ) 248 | 249 | } 250 | } 251 | 252 | /** 253 | * Dialog to choose a different app 254 | */ 255 | @Composable 256 | fun AppListDialog( 257 | context: Context, 258 | viewModel: ViewModel, 259 | onDismiss: () -> Unit, 260 | onAppSelected: (ResolveInfo) -> Unit 261 | ) { 262 | val launchableApps = remember { 263 | viewModel.getLaunchableApps(context) 264 | .sortedBy { it.loadLabel(context.packageManager).toString() } 265 | } 266 | val packageManager = context.packageManager 267 | 268 | AlertDialog( 269 | onDismissRequest = onDismiss, 270 | title = { 271 | Text(text = "Choose an app to launch") 272 | }, 273 | text = { 274 | LazyColumn { 275 | items(launchableApps) { appInfo -> 276 | val appName = appInfo.loadLabel(packageManager).toString() 277 | Row( 278 | modifier = Modifier 279 | .fillMaxWidth() 280 | .clickable { onAppSelected(appInfo) } 281 | .padding(8.dp), 282 | verticalAlignment = Alignment.CenterVertically 283 | ) { 284 | Image( 285 | bitmap = appInfo.loadIcon(packageManager).toBitmap().asImageBitmap(), 286 | contentDescription = null, 287 | modifier = Modifier.size(40.dp) 288 | ) 289 | Spacer(modifier = Modifier.width(8.dp)) 290 | Column { 291 | Text(text = appName) 292 | Text(text = appInfo.activityInfo.packageName) 293 | } 294 | } 295 | } 296 | } 297 | 298 | }, 299 | confirmButton = { 300 | TextButton(onClick = onDismiss) { 301 | Text("Close") 302 | } 303 | } 304 | ) 305 | } 306 | -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 2 | 3 | import android.app.ActivityManager 4 | import android.app.AppOpsManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.pm.PackageManager 8 | import android.content.pm.ResolveInfo 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.provider.Settings 12 | import android.util.Log 13 | import androidx.core.content.ContextCompat 14 | import androidx.lifecycle.LiveData 15 | import androidx.lifecycle.MutableLiveData 16 | import androidx.lifecycle.ViewModel 17 | 18 | class ViewModel : ViewModel() { 19 | private val _isUsageStatsPermissionGranted = MutableLiveData() 20 | val isUsageStatsPermissionGranted: LiveData get() = _isUsageStatsPermissionGranted 21 | 22 | private val _isOnTopPermissionGranted = MutableLiveData() 23 | val isOnTopPermissionGranted: LiveData get() = _isOnTopPermissionGranted 24 | 25 | private val _isNotificationPermissionGranted = MutableLiveData() 26 | val isNotificationPermissionGranted: LiveData get() = _isNotificationPermissionGranted 27 | 28 | private val _appHasBeenChoosed = MutableLiveData() 29 | val appHasBeenChoosed: LiveData get() = _appHasBeenChoosed 30 | 31 | private val _appTargetName = MutableLiveData() 32 | val appTargetName: LiveData get() = _appTargetName 33 | 34 | fun getServiceData() : serviceData { 35 | return serviceData 36 | } 37 | 38 | fun onLaunch(context: Context) { 39 | val appTargetPackage = getPreferences(context, "appTargetPackage") ?: "" 40 | val appTargetName = getPreferences(context, "appTargetName") ?: "" 41 | 42 | if (appTargetPackage.isNotEmpty() && appTargetName.isNotEmpty()) { // only load preferences if an app has been already saved 43 | setApptarget(appTargetPackage, appTargetName, context) 44 | } else { 45 | Log.d("ViewModel", "No app target set initially.") 46 | } 47 | Log.d("ViewModel", "Launched viewmodel") 48 | _isUsageStatsPermissionGranted.value = isUsageStatsPermissionGranted(context) 49 | _isOnTopPermissionGranted.value = isOnTopPermissionGranted(context) 50 | _isNotificationPermissionGranted.value = isNotificationPermissionGranted(context) 51 | } 52 | 53 | fun setApptarget(packageName: String, appName: String, context: Context) { 54 | if (serviceData.appTargetPackage != packageName && checkifServiceisRunning(context, BackgroundService::class.java)) { // only restart the service if the app is different 55 | stopService(context) 56 | startService(context) 57 | } 58 | serviceData.appTargetPackage = packageName 59 | serviceData.appTargetName = appName 60 | _appHasBeenChoosed.value = getApptargetPackage() != null 61 | _appTargetName.value = getApptargetName() ?: "" 62 | // Save preferences 63 | savePreferences(context, "appTargetPackage", packageName) 64 | savePreferences(context, "appTargetName", appName) 65 | } 66 | 67 | fun getApptargetPackage(): String? { 68 | return serviceData.appTargetPackage 69 | } 70 | 71 | fun getApptargetName(): String? { 72 | return serviceData.appTargetName 73 | } 74 | 75 | private fun startService(context: Context) { 76 | val serviceIntent = Intent(context, BackgroundService::class.java) 77 | context.startForegroundService(serviceIntent) 78 | serviceData.serviceStatus = checkifServiceisRunning(context, BackgroundService::class.java) 79 | } 80 | 81 | private fun stopService(context: Context) { 82 | val serviceIntent = Intent(context, BackgroundService::class.java) 83 | context.stopService(serviceIntent) 84 | serviceData.serviceStatus = checkifServiceisRunning(context, BackgroundService::class.java) 85 | } 86 | 87 | fun checkifServiceisRunning(context: Context, serviceClass: Class<*>): Boolean { 88 | val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 89 | for (service in activityManager.getRunningServices(Int.MAX_VALUE)) { 90 | if (serviceClass.name == service.service.className) { 91 | return true 92 | } 93 | } 94 | return false 95 | } 96 | 97 | fun setServiceStatus(context: Context, serviceStatus: Boolean) { 98 | if (serviceData.serviceStatus != serviceStatus) { 99 | serviceData.serviceStatus = serviceStatus 100 | if (serviceStatus) { 101 | startService(context) 102 | } else { 103 | stopService(context) 104 | } 105 | } 106 | } 107 | 108 | fun isUsageStatsPermissionGranted(context: Context): Boolean { 109 | val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager 110 | val mode = 111 | appOpsManager.unsafeCheckOpNoThrow( 112 | AppOpsManager.OPSTR_GET_USAGE_STATS, 113 | android.os.Process.myUid(), 114 | context.packageName 115 | ) 116 | return mode == AppOpsManager.MODE_ALLOWED 117 | } 118 | 119 | fun requestUsageStatsPermission(context: Context) { 120 | val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) 121 | context.startActivity(intent) 122 | } 123 | 124 | fun isOnTopPermissionGranted(context: Context): Boolean { 125 | val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager 126 | val mode = 127 | appOpsManager.unsafeCheckOpNoThrow( 128 | AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, 129 | android.os.Process.myUid(), 130 | context.packageName 131 | ) 132 | return mode == AppOpsManager.MODE_ALLOWED 133 | } 134 | 135 | fun requestOnTopPermission(context: Context) { 136 | val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) 137 | context.startActivity(intent) 138 | } 139 | 140 | fun isNotificationPermissionGranted(context: Context): Boolean { 141 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 142 | ContextCompat.checkSelfPermission( 143 | context, 144 | android.Manifest.permission.POST_NOTIFICATIONS 145 | ) == PackageManager.PERMISSION_GRANTED 146 | } else { 147 | // For versions below Android 13, notification permission is implicitly granted. 148 | true 149 | } 150 | } 151 | 152 | fun openNotificationPermissionSettings(context: Context) { 153 | val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { 154 | putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) 155 | } 156 | context.startActivity(intent) 157 | } 158 | 159 | fun getLaunchableApps(context: Context): List { 160 | val packageManager = context.packageManager 161 | val intent = Intent(Intent.ACTION_MAIN, null).apply { 162 | addCategory(Intent.CATEGORY_LAUNCHER) 163 | } 164 | return packageManager.queryIntentActivities(intent, 0) 165 | } 166 | 167 | fun openGitHubRepository(context: Context, repositoryUrl: String) { 168 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(repositoryUrl)) 169 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 170 | context.startActivity(intent) 171 | } 172 | 173 | fun savePreferences(context: Context, key: String, value: String) { 174 | val sharedPref = context.getSharedPreferences("com.abeja.gamecenterreplacer", Context.MODE_PRIVATE) 175 | with (sharedPref.edit()) { 176 | putString(key, value) 177 | apply() 178 | } 179 | } 180 | 181 | fun getPreferences(context: Context, key: String): String? { 182 | val sharedPref = context.getSharedPreferences("com.abeja.gamecenterreplacer", Context.MODE_PRIVATE) 183 | return sharedPref.getString(key, null) 184 | } 185 | 186 | } -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/data.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer 2 | 3 | 4 | object serviceData { 5 | var serviceStatus = false 6 | var appTargetPackage: String? = null 7 | var appTargetName: String? = null 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer.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/abeja/gamecenterreplacer/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.platform.LocalContext 13 | 14 | private val DarkColorScheme = darkColorScheme( 15 | primary = Purple80, 16 | secondary = PurpleGrey80, 17 | tertiary = Pink80 18 | ) 19 | 20 | private val LightColorScheme = lightColorScheme( 21 | primary = Purple40, 22 | secondary = PurpleGrey40, 23 | tertiary = Pink40 24 | 25 | /* Other default colors to override 26 | background = Color(0xFFFFFBFE), 27 | surface = Color(0xFFFFFBFE), 28 | onPrimary = Color.White, 29 | onSecondary = Color.White, 30 | onTertiary = Color.White, 31 | onBackground = Color(0xFF1C1B1F), 32 | onSurface = Color(0xFF1C1B1F), 33 | */ 34 | ) 35 | 36 | @Composable 37 | fun GamecenterreplacerTheme( 38 | darkTheme: Boolean = isSystemInDarkTheme(), 39 | // Dynamic color is available on Android 12+ 40 | dynamicColor: Boolean = true, 41 | content: @Composable () -> Unit 42 | ) { 43 | val colorScheme = when { 44 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 45 | val context = LocalContext.current 46 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 47 | } 48 | 49 | darkTheme -> DarkColorScheme 50 | else -> LightColorScheme 51 | } 52 | 53 | MaterialTheme( 54 | colorScheme = colorScheme, 55 | typography = Typography, 56 | content = content 57 | ) 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/abeja/gamecenterreplacer/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.abeja.gamecenterreplacer.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/res/drawable/discord_mark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/github_mark.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheRealCrazyfuy/GameSpaceReplacer/983b1c8ea25be482d5fd444589988202e2a62cdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Game Space Replacer 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |