Repository: Xposed-Modules-Repo/balti.xposed.pixelifygooglephotos
Branch: main
Commit: 5c47617d69fa
Files: 47
Total size: 117.2 KB
Directory structure:
gitextract_63__q8nw/
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── .name
│ ├── compiler.xml
│ └── misc.xml
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── xposed_init
│ ├── java/
│ │ └── balti/
│ │ └── xposed/
│ │ └── pixelifygooglephotos/
│ │ ├── ActivityMain.kt
│ │ ├── AdvancedOptionsActivity.kt
│ │ ├── Constants.kt
│ │ ├── DeviceProps.kt
│ │ ├── DeviceSpoofer.kt
│ │ ├── FeatureCustomize.kt
│ │ ├── FeatureSpoofer.kt
│ │ └── Utils.kt
│ └── res/
│ ├── drawable/
│ │ ├── ic_export.xml
│ │ ├── ic_import.xml
│ │ ├── ic_info.xml
│ │ ├── ic_launcher_background.xml
│ │ └── ic_open.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── advanced_options_activity.xml
│ │ └── feature_customize.xml
│ ├── menu/
│ │ └── menu_activity_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── module_scope.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── values-night/
│ │ └── themes.xml
│ ├── values-zh-rTW/
│ │ └── strings.xml
│ └── xml/
│ └── provider_paths.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── update_info.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
### Android template
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
================================================
FILE: .idea/.name
================================================
Pixelify Google Photos
================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/drawable/ic_export.xml" value="0.21481481481481482" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/drawable/ic_import.xml" value="0.2203125" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/drawable/ic_launcher_background.xml" value="0.1125" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/drawable/ic_share.xml" value="0.21481481481481482" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/layout/activity_main.xml" value="0.25" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/layout/advanced_options_activity.xml" value="0.1537037037037037" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/layout/feature_customize.xml" value="0.20416666666666666" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/menu/menu_activity_main.xml" value="0.20416666666666666" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.20833333333333334" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.20833333333333334" />
<entry key="..\:/AndroidStudioProjects/Pixelify-Google-Photos/app/src/main/res/xml/provider_paths.xml" value="0.20416666666666666" />
<entry key="..\:/Users/SayantanRC/AndroidStudioProjects/PixelifyGooglePhotos/app/src/main/res/layout/activity_main.xml" value="0.20416666666666666" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 BaltiApps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Pixelify-Google-Photos
LSPosed / EdXposed module to add Google Pixel features on Google Photos for any device.
Tested on EdXposed by [Jim Wu](https://github.com/MlgmXyysd)
[LSPosed module repo](https://github.com/Xposed-Modules-Repo/balti.xposed.pixelifygooglephotos.git)
[Development repo](https://github.com/BaltiApps/Pixelify-Google-Photos.git)
[Telegram group link](https://t.me/pixelifyGooglePhotos)
### Steps to use:
1. Install Magisk, [LSPosed](https://github.com/LSPosed/LSPosed) Or [EdXposed](https://github.com/ElderDrivers/EdXposed).
2. Install the apk of this app (available from [Releases](https://github.com/BaltiApps/Pixelify-Google-Photos/releases) page.)
3. Open LSPosed / EdXposed app and enable the module. For LSPosed, Google Photos will be automatically selected.
4. Reboot. Enjoy. (If needed, you might need to clear data of Google Photos app).
### How does this module work?
It simply hooks on to `hasSystemFeature()` method under `android.app.ApplicationPackageManager` class.
Then when Google Photos checks the relevant features which are expected only on Pixel devices, the module passes `true`.
Thus Google Photos enables Pixel-Exclusive features.
The features being "spoofed" can be found from:
[Dot OS sources](https://github.com/DotOS/android_vendor_dot/blob/55f1c26bb6dbb1175d96cf538ae113618caf7d06/prebuilt/common/etc/pixel_2016_exclusive.xml)
[PixelFeatureDrops magisk module](https://github.com/ayush5harma/PixelFeatureDrops/tree/master/system/etc/sysconfig)
This module can also spoof some of the `build.prop` information like `BRAND`, `MANUFACTURER`, `MODEL`, `FINGERPRINT` of some Pixel devices.
### Disclaimer!!
The user takes sole responsibility for any damage that might arise due to use of this module.
This includes physical damage (to device), injury, data loss, and also legal matters.
This project was made as a learning initiative and the developer cannot be held liable in any way for the use of it.
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 31
defaultConfig {
applicationId "balti.xposed.pixelifygooglephotos"
minSdk 21
targetSdk 31
versionCode 5
versionName "4.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
// Xposed API
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="balti.xposed.pixelifygooglephotos">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<queries>
<package android:name="com.google.android.apps.photos" />
</queries>
<application
android:label="@string/app_name"
android:supportsRtl="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.PixelifyGooglePhotos" >
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Add Google Pixel features in Google Photos on any device." />
<meta-data
android:name="xposedsharedprefs"
android:value="true" />
<meta-data
android:name="xposedminversion"
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/module_scope" />
<activity android:name=".ActivityMain"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".FeatureCustomize"/>
<activity android:name=".AdvancedOptionsActivity"/>
<!--Guide at: https://infinum.com/the-capsized-eight/share-files-using-fileprovider-->
<provider
android:authorities="${applicationId}"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
================================================
FILE: app/src/main/assets/xposed_init
================================================
balti.xposed.pixelifygooglephotos.FeatureSpoofer
balti.xposed.pixelifygooglephotos.DeviceSpoofer
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/ActivityMain.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.app.Activity
import android.content.Intent
import android.graphics.Paint
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.FileProvider
import balti.xposed.pixelifygooglephotos.Constants.CONF_EXPORT_NAME
import balti.xposed.pixelifygooglephotos.Constants.FIELD_LATEST_VERSION_CODE
import balti.xposed.pixelifygooglephotos.Constants.PREF_DEVICE_TO_SPOOF
import balti.xposed.pixelifygooglephotos.Constants.PREF_ENABLE_VERBOSE_LOGS
import balti.xposed.pixelifygooglephotos.Constants.PREF_LAST_VERSION
import balti.xposed.pixelifygooglephotos.Constants.PREF_OVERRIDE_ROM_FEATURE_LEVELS
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_MANUAL
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_FEATURES_LIST
import balti.xposed.pixelifygooglephotos.Constants.PREF_STRICTLY_CHECK_GOOGLE_PHOTOS
import balti.xposed.pixelifygooglephotos.Constants.RELEASES_URL
import balti.xposed.pixelifygooglephotos.Constants.RELEASES_URL2
import balti.xposed.pixelifygooglephotos.Constants.SHARED_PREF_FILE_NAME
import balti.xposed.pixelifygooglephotos.Constants.TELEGRAM_GROUP
import balti.xposed.pixelifygooglephotos.Constants.UPDATE_INFO_URL
import balti.xposed.pixelifygooglephotos.Constants.UPDATE_INFO_URL2
import com.google.android.material.snackbar.Snackbar
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.net.URL
class ActivityMain: AppCompatActivity(R.layout.activity_main) {
/**
* Normally [MODE_WORLD_READABLE] causes a crash.
* But if "xposedsharedprefs" flag is present in AndroidManifest,
* then the file is accordingly taken care by lsposed framework.
*
* If an exception is thrown, means module is not enabled,
* hence Android throws a security exception.
*/
private val pref by lazy {
try {
getSharedPreferences(SHARED_PREF_FILE_NAME, MODE_WORLD_READABLE)
} catch (_: Exception){
null
}
}
private fun showRebootSnack(){
if (pref == null) return // don't display snackbar if module not active.
val rootView = findViewById<ScrollView>(R.id.root_view_for_snackbar)
Snackbar.make(rootView, R.string.please_force_stop_google_photos, Snackbar.LENGTH_SHORT).show()
}
/**
* Animate the "Feature flags changed" textview and hide it after showing for sometime.
*/
private fun peekFeatureFlagsChanged(textView: TextView){
textView.run {
alpha = 1.0f
animate().alpha(0.0f).apply {
duration = 1000
startDelay = 3000
}.start()
}
}
private val utils by lazy { Utils() }
/**
* Activity launcher for [FeatureCustomize] activity.
* If user presses "Save" on [FeatureCustomize] activity, then result code is RESULT_OK.
* Then show prompt to force stop Google Photos.
*/
private val childActivityLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
showRebootSnack()
}
}
/**
* Close and reopen the activity.
* For some reason, invalidate or recreate() does not refresh the switches.
*/
private fun restartActivity(){
finish()
startActivity(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* Check if [pref] is not null. If it is, then module is not enabled.
*/
if (pref == null){
AlertDialog.Builder(this)
.setMessage(R.string.module_not_enabled)
.setPositiveButton(R.string.close) {_, _ ->
finish()
}
.setCancelable(false)
.show()
}
/**
* Link to xml views.
*/
val resetSettings = findViewById<Button>(R.id.reset_settings)
val customizeFeatureFlags = findViewById<LinearLayout>(R.id.customize_feature_flags)
val featureFlagsChanged = findViewById<TextView>(R.id.feature_flags_changed)
val overrideROMFeatureLevels = findViewById<SwitchCompat>(R.id.override_rom_feature_levels)
val switchEnforceGooglePhotos = findViewById<SwitchCompat>(R.id.spoof_only_in_google_photos_switch)
val deviceSpooferSpinner = findViewById<Spinner>(R.id.device_spoofer_spinner)
val forceStopGooglePhotos = findViewById<Button>(R.id.force_stop_google_photos)
val openGooglePhotos = findViewById<ImageButton>(R.id.open_google_photos)
val advancedOptions = findViewById<TextView>(R.id.advanced_options)
val telegramLink = findViewById<TextView>(R.id.telegram_group)
val updateAvailableLink = findViewById<TextView>(R.id.update_available_link)
val confExport = findViewById<ImageButton>(R.id.conf_export)
val confImport = findViewById<ImageButton>(R.id.conf_import)
/**
* Set default spoof device to [DeviceProps.defaultDeviceName].
* Set check for google photos as `false`.
* Set default feature levels to spoof.
* Restart the activity.
*/
resetSettings.setOnClickListener {
pref?.edit()?.run {
putString(PREF_DEVICE_TO_SPOOF, DeviceProps.defaultDeviceName)
putBoolean(PREF_OVERRIDE_ROM_FEATURE_LEVELS, true)
putBoolean(PREF_STRICTLY_CHECK_GOOGLE_PHOTOS, true)
putStringSet(
PREF_SPOOF_FEATURES_LIST,
DeviceProps.defaultFeatures.map { it.displayName }.toSet()
)
putBoolean(PREF_ENABLE_VERBOSE_LOGS, false)
putBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, false)
putString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)
apply()
}
restartActivity()
}
/**
* See [FeatureSpoofer.featuresNotToSpoof].
*/
overrideROMFeatureLevels.apply {
isChecked = pref?.getBoolean(PREF_OVERRIDE_ROM_FEATURE_LEVELS, true) ?: false
setOnCheckedChangeListener { _, isChecked ->
pref?.edit()?.run {
putBoolean(PREF_OVERRIDE_ROM_FEATURE_LEVELS, isChecked)
apply()
showRebootSnack()
}
}
}
/**
* See [FeatureSpoofer].
*/
switchEnforceGooglePhotos.apply {
isChecked = pref?.getBoolean(PREF_STRICTLY_CHECK_GOOGLE_PHOTOS, true) ?: false
setOnCheckedChangeListener { _, isChecked ->
pref?.edit()?.run {
putBoolean(PREF_STRICTLY_CHECK_GOOGLE_PHOTOS, isChecked)
apply()
showRebootSnack()
}
}
}
/**
* See [DeviceSpoofer].
*/
deviceSpooferSpinner.apply {
val deviceNames = DeviceProps.allDevices.map { it.deviceName }
val aa = ArrayAdapter(this@ActivityMain,android.R.layout.simple_spinner_item, deviceNames)
aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter = aa
val defaultSelection = pref?.getString(PREF_DEVICE_TO_SPOOF, DeviceProps.defaultDeviceName)
/** Second argument is `false` to prevent calling [peekFeatureFlagsChanged] on initialization */
setSelection(aa.getPosition(defaultSelection), false)
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val deviceName = aa.getItem(position)
pref?.edit()?.apply {
putString(PREF_DEVICE_TO_SPOOF, deviceName)
putStringSet(
PREF_SPOOF_FEATURES_LIST,
DeviceProps.getFeaturesUpToFromDeviceName(deviceName)
)
apply()
}
peekFeatureFlagsChanged(featureFlagsChanged)
showRebootSnack()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
advancedOptions.apply {
paintFlags = Paint.UNDERLINE_TEXT_FLAG
setOnClickListener {
childActivityLauncher.launch(Intent(this@ActivityMain, AdvancedOptionsActivity::class.java))
}
}
/**
* See [Utils.forceStopPackage].
*/
forceStopGooglePhotos.setOnClickListener {
utils.forceStopPackage(Constants.PACKAGE_NAME_GOOGLE_PHOTOS, this)
}
/**
* See [Utils.openApplication].
*/
openGooglePhotos.setOnClickListener {
utils.openApplication(Constants.PACKAGE_NAME_GOOGLE_PHOTOS, this)
}
/**
* Launch [FeatureCustomize] to fine select the features.
*/
customizeFeatureFlags.setOnClickListener {
childActivityLauncher.launch(Intent(this, FeatureCustomize::class.java))
}
/**
* Open telegram group.
*/
telegramLink.apply {
paintFlags = Paint.UNDERLINE_TEXT_FLAG
setOnClickListener {
openWebLink(TELEGRAM_GROUP)
}
}
/**
* Open config share options.
* Also see [Utils.writeConfigFile].
*/
confExport.setOnClickListener {
AlertDialog.Builder(this).apply {
setTitle(R.string.export_config)
setMessage(R.string.export_config_desc)
setPositiveButton(R.string.share){_, _ ->
shareConfFile()
}
setNegativeButton(R.string.save){_, _ ->
saveConfFile()
}
setNeutralButton(android.R.string.cancel, null)
}
.show()
}
confImport.setOnClickListener {
AlertDialog.Builder(this).apply {
setTitle(R.string.import_config)
setMessage(R.string.import_config_desc)
setPositiveButton(android.R.string.ok){_, _ ->
importConfFile()
}
setNegativeButton(android.R.string.cancel, null)
}
.show()
}
/**
* Check if changelogs need to be shown when upgrading from older version.
*/
pref?.apply {
val thisVersion = BuildConfig.VERSION_CODE
if (getInt(PREF_LAST_VERSION, 0) < thisVersion){
showChangeLog()
edit().apply {
putInt(PREF_LAST_VERSION, thisVersion)
apply()
}
}
}
/**
* Check for updates in background thread.
* Yes AsyncTask is deprecated, but it works fine and for such a short network operation
* it is useless to try coroutine or something like that.
*/
AsyncTask.execute {
isUpdateAvailable()?.let { url ->
runOnUiThread {
updateAvailableLink.apply {
paintFlags = Paint.UNDERLINE_TEXT_FLAG
visibility = View.VISIBLE
setOnClickListener {
openWebLink(url)
}
}
}
}
}
}
/**
* Method to show latest changes.
*/
private fun showChangeLog(){
AlertDialog.Builder(this)
.setTitle(R.string.version_head)
.setMessage(R.string.version_desc)
.setPositiveButton(android.R.string.ok, null)
.show()
}
/**
* Populate menu.
* Menu contains option to show changelog.
*/
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_activity_main, menu)
return super.onCreateOptionsMenu(menu)
}
/**
* Click listener on menu.
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.menu_changelog -> showChangeLog()
}
return super.onOptionsItemSelected(item)
}
/**
* Check if update is available. Return url string of Github Releases page if update is present.
* Else returns null.
*
* This checks for update in two repositories.
* The original BaltiApps repository, as well as LSPosed repository.
* If update is available in any of them it send the respective repo's Release page link.
*/
private fun isUpdateAvailable(): String? {
fun getUpdateStatus(url: String): Boolean {
var jsonString = ""
val baos = ByteArrayOutputStream()
/**
* Get contents of the file into a string.
*/
try {
URL(url).openStream().use { input ->
baos.use { output ->
input.copyTo(output)
}
jsonString = baos.toString()
}
} catch (_: Exception) {
return false
}
/**
* Parse the string as a JSON object.
*/
return if (jsonString.isNotBlank()) {
try {
val json = JSONObject(jsonString)
val remoteVersion = json.getInt(FIELD_LATEST_VERSION_CODE)
BuildConfig.VERSION_CODE < remoteVersion
} catch (_: Exception) {
false
}
} else false
}
/**
* Check both repositories.
*/
return when {
getUpdateStatus(UPDATE_INFO_URL) -> RELEASES_URL
getUpdateStatus(UPDATE_INFO_URL2) -> RELEASES_URL2
else -> null
}
}
/**
* Open any url link
*/
fun openWebLink(url: String){
startActivity(Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(url)
})
}
/**
* Creates configuration export file to internal cache.
* Shares it to other apps.
*/
private fun shareConfFile(){
try {
val confFile = File(cacheDir, CONF_EXPORT_NAME)
val uriFromFile = Uri.fromFile(confFile)
confFile.delete()
utils.writeConfigFile(this, uriFromFile, pref)
val confFileShareUri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, confFile)
else uriFromFile
Intent().run {
action = Intent.ACTION_SEND
type = "*/*"
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
this.putExtra(Intent.EXTRA_STREAM, confFileShareUri)
startActivity(Intent.createChooser(this, getString(R.string.share_config_file)))
}
}
catch (e: Exception){
e.printStackTrace()
Toast.makeText(this, "${getString(R.string.share_error)}: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
/**
* Open a storage location on the device to export the configuration as a document.
* Uses intent with action [Intent.ACTION_CREATE_DOCUMENT]
* Also see [configCreateLauncher].
*
* Derived from https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386
*/
private fun saveConfFile(){
val openIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
// filter to only show openable items.
addCategory(Intent.CATEGORY_OPENABLE)
// Create a file with the requested Mime type
type = "*/*"
putExtra(Intent.EXTRA_TITLE, CONF_EXPORT_NAME)
}
Toast.makeText(this, R.string.select_a_location, Toast.LENGTH_SHORT).show()
configCreateLauncher.launch(openIntent)
}
/**
* Intent launcher to start system file picker UI to select location of export.
* The Uri of the location is present in result.
* Then call [Utils.writeConfigFile] using that Uri.
*/
private val configCreateLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
try {
if (it.resultCode == Activity.RESULT_OK) {
utils.writeConfigFile(this, it.data!!.data!!, pref)
Toast.makeText(this, R.string.export_complete, Toast.LENGTH_SHORT).show()
}
}
catch (e: Exception){
e.printStackTrace()
Toast.makeText(this, "${getString(R.string.share_error)}: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
/**
* Read a JSON file to get the configurations.
* Opens system file picker to select the file.
*
* https://developer.android.com/training/data-storage/shared/documents-files#open-file
*/
private fun importConfFile(){
val openIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
configOpenLauncher.launch(openIntent)
}
private val configOpenLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
try {
if (it.resultCode == Activity.RESULT_OK) {
utils.readConfigFile(this, it.data!!.data!!, pref)
Toast.makeText(this, R.string.import_complete, Toast.LENGTH_SHORT).show()
restartActivity()
}
}
catch (e: Exception){
e.printStackTrace()
Toast.makeText(this, "${getString(R.string.read_error)}: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/AdvancedOptionsActivity.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.app.Activity
import android.os.Bundle
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import balti.xposed.pixelifygooglephotos.Constants.PREF_DEVICE_TO_SPOOF
import balti.xposed.pixelifygooglephotos.Constants.PREF_ENABLE_VERBOSE_LOGS
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_MANUAL
import balti.xposed.pixelifygooglephotos.Constants.SHARED_PREF_FILE_NAME
class AdvancedOptionsActivity: AppCompatActivity(R.layout.advanced_options_activity) {
private val pref by lazy {
getSharedPreferences(SHARED_PREF_FILE_NAME, MODE_WORLD_READABLE)
}
private val verboseLogging by lazy { findViewById<CheckBox>(R.id.verbose_logging) }
private val deviceNameLabel by lazy { findViewById<TextView>(R.id.device_name_label) }
private val androidVersionRadioGroup by lazy { findViewById<RadioGroup>(R.id.android_version_radio_group) }
private val deviceAndroidVersion by lazy { findViewById<TextView>(R.id.device_android_version) }
private val androidVersionSpinner by lazy { findViewById<Spinner>(R.id.android_version_spinner) }
private val save by lazy { findViewById<Button>(R.id.save_advanced_option) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (pref == null) return
verboseLogging.isChecked = pref.getBoolean(PREF_ENABLE_VERBOSE_LOGS, false)
/**
* Get the current spoofing device an its android version.
*/
val deviceNameInPreference = pref.getString(PREF_DEVICE_TO_SPOOF, DeviceProps.defaultDeviceName)
val spoofDevice = DeviceProps.getDeviceProps(deviceNameInPreference)
deviceNameLabel.text = spoofDevice?.deviceName
deviceAndroidVersion.text = spoofDevice?.androidVersion?.label
/**
* String list of all [DeviceProps.AndroidVersion.label].
*/
val allVersionLabels = DeviceProps.allAndroidVersions.map { it.label }
/**
* Set spinner for manually setting android version.
*/
androidVersionSpinner.apply {
val aa = ArrayAdapter(
this@AdvancedOptionsActivity,
android.R.layout.simple_spinner_item,
allVersionLabels
)
aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter = aa
val defaultSelection = pref?.getString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)
setSelection(aa.getPosition(defaultSelection))
}
/**
* Set default radio button.
*/
androidVersionRadioGroup.apply {
// hide spinner if not to be manually set.
setOnCheckedChangeListener { _, checkedId ->
androidVersionSpinner.isVisible = checkedId == R.id.manually_set_android_version
}
val manualVersion = pref.getString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)?.trim()
check(
when {
pref.getBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, false) -> R.id.follow_spoof_device_version
manualVersion != null && manualVersion in allVersionLabels -> R.id.manually_set_android_version
else -> R.id.dont_spoof_android_version
}
)
}
save.setOnClickListener {
savePreferences()
}
}
/**
* Function to save all options.
*/
private fun savePreferences(){
pref?.edit()?.run {
/** Option for verbose log. */
putBoolean(PREF_ENABLE_VERBOSE_LOGS, verboseLogging.isChecked)
when(androidVersionRadioGroup.checkedRadioButtonId){
/** If not to spoof, disable both preferences */
R.id.dont_spoof_android_version -> {
putBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, false)
putString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)
}
/** Enable only [PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE]
* if to follow spoof device android version.
*/
R.id.follow_spoof_device_version -> {
putBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, true)
putString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)
}
/**
* If manually set, then get the label from spinner itself
* and set the value for preference [PREF_SPOOF_ANDROID_VERSION_MANUAL].
*/
R.id.manually_set_android_version -> {
putBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, false)
putString(
PREF_SPOOF_ANDROID_VERSION_MANUAL,
androidVersionSpinner.selectedItem.toString()
)
}
}
apply()
}
setResult(Activity.RESULT_OK)
finish()
}
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/Constants.kt
================================================
package balti.xposed.pixelifygooglephotos
object Constants {
val PACKAGE_NAME_GOOGLE_PHOTOS = "com.google.android.apps.photos"
val TELEGRAM_GROUP = "https://t.me/pixelifyGooglePhotos"
val UPDATE_INFO_URL = "https://raw.githubusercontent.com/BaltiApps/Pixelify-Google-Photos/main/update_info.json"
val UPDATE_INFO_URL2 = "https://raw.githubusercontent.com/Xposed-Modules-Repo/balti.xposed.pixelifygooglephotos/main/update_info.json"
val RELEASES_URL = "https://github.com/BaltiApps/Pixelify-Google-Photos/releases"
val RELEASES_URL2 = "https://github.com/Xposed-Modules-Repo/balti.xposed.pixelifygooglephotos/releases"
val FIELD_LATEST_VERSION_CODE = "latest_version_code"
val SHARED_PREF_FILE_NAME = "prefs"
val CONF_EXPORT_NAME = "pgp_conf.json"
val PREF_SPOOF_FEATURES_LIST = "PREF_SPOOF_FEATURES_LIST"
val PREF_DEVICE_TO_SPOOF = "PREF_DEVICE_TO_SPOOF"
val PREF_STRICTLY_CHECK_GOOGLE_PHOTOS = "PREF_STRICTLY_CHECK_GOOGLE_PHOTOS"
val PREF_OVERRIDE_ROM_FEATURE_LEVELS = "PREF_OVERRIDE_ROM_FEATURE_LEVELS"
val PREF_ENABLE_VERBOSE_LOGS = "PREF_ENABLE_VERBOSE_LOGS"
val PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE = "PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE"
val PREF_SPOOF_ANDROID_VERSION_MANUAL = "PREF_SPOOF_ANDROID_VERSION_MANUAL"
val PREF_LAST_VERSION = "PREF_LAST_VERSION"
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/DeviceProps.kt
================================================
package balti.xposed.pixelifygooglephotos
/**
* Build values taken from:
* Pixel 6:
* https://github.com/DotOS/android_frameworks_base/blob/dot12/core/java/com/android/internal/util/custom/PixelPropsUtils.java
* Pixel 2:
* https://gist.github.com/markstachowski/4069f15c5c989827b9e64c0aec045434
* Pixel 5a:
* https://github.com/LineageOS/android_device_google_barbet/blob/lineage-18.1/lineage_barbet.mk
* All other pixels:
* https://github.com/orgs/Pixel-Props/repositories
* Also from
* https://github.com/DotOS/android_frameworks_base/commit/3f7ea7d070017ed1f38035333f084865865698b2
*
* Features taken from:
* https://t.me/PixelProps
* https://github.com/DotOS/android_vendor_dot/blob/dot12/prebuilt/common/etc/pixel_2016_exclusive.xml
* https://github.com/ayush5harma/PixelFeatureDrops/tree/master/system/etc/sysconfig
*/
object DeviceProps {
/**
* Class to store different feature flags for different pixels.
* @param displayName String to show to user to customize flag selection. Example "Pixel 2020"
* Also note that these display names are what is actually stored in shared preferences.
* The actual feature flags are then derived from the display names.
* @param featureFlags List of actual features spoofed to Google Photos for that particular [displayName].
* Example, for [displayName] = "Pixel 2020", [featureFlags] = listOf("com.google.android.feature.PIXEL_2020_EXPERIENCE")
*/
class Features(
val displayName: String,
val featureFlags: List<String>,
){
constructor(displayName: String, vararg featureFlags: String) : this(
displayName,
featureFlags.toList()
)
constructor(displayName: String, singleFeature: String) : this(
displayName,
listOf(singleFeature)
)
}
/**
* List of all possible feature flags.
* CHRONOLOGY IS IMPORTANT. Elements are arranged in accordance of device release date.
*/
val allFeatures = listOf(
Features("Pixel 2016", // Pixel XL
"com.google.android.apps.photos.NEXUS_PRELOAD",
"com.google.android.apps.photos.nexus_preload",
"com.google.android.feature.PIXEL_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_PRELOAD",
"com.google.android.apps.photos.PIXEL_2016_PRELOAD",
),
Features("Pixel 2017", // Pixel 2
"com.google.android.feature.PIXEL_2017_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2017_PRELOAD"
),
Features("Pixel 2018", // Pixel 3 XL
"com.google.android.feature.PIXEL_2018_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2018_PRELOAD"
),
Features("Pixel 2019 mid-year", // Pixel 3a XL
"com.google.android.feature.PIXEL_2019_MIDYEAR_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2019_MIDYEAR_PRELOAD",
),
Features("Pixel 2019", // Pixel 4 XL
"com.google.android.feature.PIXEL_2019_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2019_PRELOAD",
),
Features("Pixel 2020 mid-year", // Pixel 4a
"com.google.android.feature.PIXEL_2020_MIDYEAR_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2020_MIDYEAR_PRELOAD",
),
Features("Pixel 2020", // Pixel 5
"com.google.android.feature.PIXEL_2020_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2020_PRELOAD",
),
Features("Pixel 2021 mid-year", // Pixel 5a
"com.google.android.feature.PIXEL_2021_MIDYEAR_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2021_MIDYEAR_PRELOAD",
),
Features("Pixel 2021", // Pixel 6 Pro
"com.google.android.feature.PIXEL_2021_EXPERIENCE",
"com.google.android.apps.photos.PIXEL_2021_PRELOAD",
),
)
/**
* Example if [featureLevel] = "Pixel 2020", return will have
* list of all elements from [allFeatures] from "Pixel 2016" i.e. index = 0,
* to "Pixel 2020" i.e index = 6, both inclusive.
*/
private fun getFeaturesUpTo(featureLevel: String): List<Features> {
val allFeatureDisplayNames = allFeatures.map { it.displayName }
val levelIndex = allFeatureDisplayNames.indexOf(featureLevel)
return if (levelIndex == -1) listOf()
else {
allFeatures.withIndex().filter { it.index <= levelIndex }.map { it.value }
}
}
/**
* Class storing android version information to be faked.
*
* @param label Just a string to show the user. Not spoofed.
* @param release Corresponds to `ro.build.version.release`. Example values: "12", "11", "8.1.0" etc.
* @param sdk Corresponds to `ro.build.version.sdk`.
*/
data class AndroidVersion(
val label: String,
val release: String,
val sdk: Int,
){
fun getAsMap() = hashMapOf(
Pair("RELEASE", release),
Pair("SDK_INT", sdk),
Pair("SDK", sdk.toString()),
)
}
/**
* List of all major android versions.
* Pixel 1 series launched with nougat, so that is the lowest version.
*/
val allAndroidVersions = listOf(
AndroidVersion("Nougat 7.1.2", "7.1.2", 25),
AndroidVersion("Oreo 8.1.0", "8.1.0", 27),
AndroidVersion("Pie 9.0", "9", 28),
AndroidVersion("Q 10.0", "10", 29),
AndroidVersion("R 11.0", "11", 30),
AndroidVersion("S 12.0", "12", 31),
)
/**
* Get instance of [AndroidVersion] from specified [label].
* Send null if no such label.
*/
fun getAndroidVersionFromLabel(label: String) = allAndroidVersions.find { it.label == label }
/**
* Class to contain device names and their respective build properties.
* @param deviceName Actual device names, example "Pixel 4a".
* @param props Contains the device properties to spoof.
* @param featureLevelName Points to the features expected to be spoofed from [allFeatures],
* from "Pixel 2016" up to this level.
*/
data class DeviceEntries(
val deviceName: String,
val props: HashMap<String, String>,
val featureLevelName: String,
val androidVersion: AndroidVersion?,
)
/**
* List of all devices and their build props and their required feature levels.
*/
val allDevices = listOf(
DeviceEntries("None", hashMapOf(), "None", null),
DeviceEntries(
"Pixel XL", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "marlin"),
Pair("PRODUCT", "marlin"),
Pair("MODEL", "Pixel XL"),
Pair("FINGERPRINT", "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys"),
),
"Pixel 2016",
getAndroidVersionFromLabel("Q 10.0"),
),
DeviceEntries(
"Pixel 2", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "walleye"),
Pair("PRODUCT", "walleye"),
Pair("MODEL", "Pixel 2"),
Pair("FINGERPRINT", "google/walleye/walleye:8.1.0/OPM1.171019.021/4565141:user/release-keys"),
),
"Pixel 2017",
getAndroidVersionFromLabel("Oreo 8.1.0"),
),
DeviceEntries(
"Pixel 3 XL", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "crosshatch"),
Pair("PRODUCT", "crosshatch"),
Pair("MODEL", "Pixel 3 XL"),
Pair("FINGERPRINT", "google/crosshatch/crosshatch:11/RQ3A.211001.001/7641976:user/release-keys"),
),
"Pixel 2018",
getAndroidVersionFromLabel("R 11.0"),
),
DeviceEntries(
"Pixel 3a XL", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "bonito"),
Pair("PRODUCT", "bonito"),
Pair("MODEL", "Pixel 3a XL"),
Pair("FINGERPRINT", "google/bonito/bonito:11/RQ3A.211001.001/7641976:user/release-keys"),
),
"Pixel 2019 mid-year",
getAndroidVersionFromLabel("R 11.0"),
),
DeviceEntries(
"Pixel 4 XL", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "coral"),
Pair("PRODUCT", "coral"),
Pair("MODEL", "Pixel 4 XL"),
Pair("FINGERPRINT", "google/coral/coral:12/SP1A.211105.002/7743617:user/release-keys"),
),
"Pixel 2019",
getAndroidVersionFromLabel("S 12.0"),
),
DeviceEntries(
"Pixel 4a", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "sunfish"),
Pair("PRODUCT", "sunfish"),
Pair("MODEL", "Pixel 4a"),
Pair("FINGERPRINT", "google/sunfish/sunfish:11/RQ3A.211001.001/7641976:user/release-keys"),
),
"Pixel 2020 mid-year",
getAndroidVersionFromLabel("R 11.0"),
),
DeviceEntries(
"Pixel 5", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "redfin"),
Pair("PRODUCT", "redfin"),
Pair("MODEL", "Pixel 5"),
Pair("FINGERPRINT", "google/redfin/redfin:12/SP1A.211105.003/7757856:user/release-keys"),
),
"Pixel 2020",
getAndroidVersionFromLabel("S 12.0"),
),
DeviceEntries(
"Pixel 5a", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "barbet"),
Pair("PRODUCT", "barbet"),
Pair("MODEL", "Pixel 5a"),
Pair("FINGERPRINT", "google/barbet/barbet:11/RD2A.211001.002/7644766:user/release-keys"),
),
"Pixel 2021 mid-year",
getAndroidVersionFromLabel("R 11.0"),
),
DeviceEntries(
"Pixel 6 Pro", hashMapOf(
Pair("BRAND", "google"),
Pair("MANUFACTURER", "Google"),
Pair("DEVICE", "raven"),
Pair("PRODUCT", "raven"),
Pair("MODEL", "Pixel 6 Pro"),
Pair("FINGERPRINT", "google/raven/raven:12/SD1A.210817.036/7805805:user/release-keys"),
),
"Pixel 2021",
getAndroidVersionFromLabel("S 12.0"),
),
)
/**
* Get instance of [DeviceEntries] from a supplied [deviceName].
*/
fun getDeviceProps(deviceName: String?) = allDevices.find { it.deviceName == deviceName }
/**
* Call [getFeaturesUpTo] using a device name rather than feature level.
* Used in spinner in main activity.
*/
fun getFeaturesUpToFromDeviceName(deviceName: String?): Set<String>{
return getDeviceProps(deviceName)?.let {
getFeaturesUpTo(it.featureLevelName).map { it.displayName }.toSet()
}?: setOf()
}
/**
* Default name of device to spoof.
*/
val defaultDeviceName = "Pixel 5"
/**
* Default feature level to spoof up to. Corresponds to what is expected for the device in [defaultDeviceName].
*/
val defaultFeatures = getFeaturesUpTo("Pixel 2020")
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/DeviceSpoofer.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.os.Build
import android.util.Log
import balti.xposed.pixelifygooglephotos.Constants.PACKAGE_NAME_GOOGLE_PHOTOS
import balti.xposed.pixelifygooglephotos.Constants.PREF_DEVICE_TO_SPOOF
import balti.xposed.pixelifygooglephotos.Constants.PREF_ENABLE_VERBOSE_LOGS
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_MANUAL
import balti.xposed.pixelifygooglephotos.Constants.PREF_STRICTLY_CHECK_GOOGLE_PHOTOS
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
/**
* Codenames of pixels:
* https://oneandroid.net/all-google-pixel-codenames-from-sailfish-to-redfin/
*
* Device properties stored in [DeviceProps].
*/
class DeviceSpoofer: IXposedHookLoadPackage {
/**
* Simple message to log messages in lsposed log as well as android log.
*/
private fun log(message: String){
XposedBridge.log("PixelifyGooglePhotos: $message")
Log.d("PixelifyGooglePhotos", message)
}
/**
* To read preference of user.
*/
private val pref by lazy {
XSharedPreferences(BuildConfig.APPLICATION_ID, Constants.SHARED_PREF_FILE_NAME)
}
private val verboseLog: Boolean by lazy {
pref.getBoolean(PREF_ENABLE_VERBOSE_LOGS, false)
}
/**
* This will always be null if the user has not chosen to spoof android version.
* If not null, then following will be spoofed:
* [Build.VERSION.RELEASE], [Build.VERSION.SDK_INT]
*
* @see DeviceProps.AndroidVersion
*/
private val androidVersionToSpoof: DeviceProps.AndroidVersion? by lazy {
if (pref.getBoolean(PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE, false))
finalDeviceToSpoof?.androidVersion
else {
pref.getString(PREF_SPOOF_ANDROID_VERSION_MANUAL, null)?.let {
DeviceProps.getAndroidVersionFromLabel(it)
}
}
}
/**
* This is the final device to spoof.
* By default use Pixel 5.
*/
private val finalDeviceToSpoof by lazy {
val deviceName = pref.getString(PREF_DEVICE_TO_SPOOF, DeviceProps.defaultDeviceName)
log("Device spoof: $deviceName")
DeviceProps.getDeviceProps(deviceName)
}
/**
* Inspired by:
* https://github.com/itsuki-t/FakeDeviceData/blob/master/src/jp/rmitkt/xposed/fakedevicedata/FakeDeviceData.java
*/
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
/**
* If user selects to never use this on any other app other than Google photos,
* then check package name and return if necessary.
*/
if (pref.getBoolean(PREF_STRICTLY_CHECK_GOOGLE_PHOTOS, true) &&
lpparam?.packageName != PACKAGE_NAME_GOOGLE_PHOTOS) return
log("Loaded DeviceSpoofer for ${lpparam?.packageName}")
log("Device spoof: ${finalDeviceToSpoof?.deviceName}")
finalDeviceToSpoof?.props?.run {
if (keys.isEmpty()) return
val classLoader = lpparam?.classLoader ?: return
val classBuild = XposedHelpers.findClass("android.os.Build", classLoader)
keys.forEach {
XposedHelpers.setStaticObjectField(classBuild, it, this[it])
if (verboseLog) log("DEVICE PROPS: $it - ${this[it]}")
}
}
androidVersionToSpoof?.getAsMap()?.run {
val classLoader = lpparam?.classLoader ?: return
val classBuild = XposedHelpers.findClass("android.os.Build.VERSION", classLoader)
keys.forEach {
XposedHelpers.setStaticObjectField(classBuild, it, this[it])
if (verboseLog) log("VERSION SPOOF: $it - ${this[it]}")
}
}
}
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/FeatureCustomize.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.app.Activity
import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_FEATURES_LIST
import balti.xposed.pixelifygooglephotos.Constants.SHARED_PREF_FILE_NAME
/**
* Provides granular controls for selecting some specific feature flags.
*/
class FeatureCustomize: AppCompatActivity(R.layout.feature_customize) {
private val pref by lazy {
getSharedPreferences(SHARED_PREF_FILE_NAME, MODE_WORLD_READABLE)
}
/**
* Set of feature names enabled by the user, fetched from shared prefs.
* If nothing is enabled, use default case from [DeviceProps.defaultFeatures].
*/
private val enabledFeaturesNames: Set<String> by lazy {
val defaultFeatures = DeviceProps.defaultFeatures
val defaultFeatureLevelsName = defaultFeatures.map { it.displayName }.toSet()
pref.getStringSet(PREF_SPOOF_FEATURES_LIST, defaultFeatureLevelsName)?: setOf()
}
private val utils by lazy { Utils() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val checkboxHolder = findViewById<LinearLayout>(R.id.feature_checkbox_holder)
val saveButton = findViewById<Button>(R.id.save_features)
/**
* Populate checkboxes in for each feature name.
*/
DeviceProps.allFeatures.withIndex().forEach {
val checkBox = CheckBox(this)
checkBox.apply {
text = it.value.displayName
id = it.index + 1
isChecked = text in enabledFeaturesNames
}
checkboxHolder.addView(checkBox)
}
/**
* When "Save" button is pressed, store the selected checkbox values in shared prefs.
* Then close the activity and send RESULT_OK
*/
saveButton.setOnClickListener {
val checkedFeatureNames =
checkboxHolder.children.filter { it is CheckBox && it.isChecked }
.map { (it as CheckBox).text.toString() }.toSet()
pref.edit().apply {
putStringSet(PREF_SPOOF_FEATURES_LIST, checkedFeatureNames)
apply()
}
setResult(Activity.RESULT_OK)
finish()
}
}
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/FeatureSpoofer.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.util.Log
import balti.xposed.pixelifygooglephotos.Constants.PACKAGE_NAME_GOOGLE_PHOTOS
import balti.xposed.pixelifygooglephotos.Constants.PREF_ENABLE_VERBOSE_LOGS
import balti.xposed.pixelifygooglephotos.Constants.PREF_OVERRIDE_ROM_FEATURE_LEVELS
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_FEATURES_LIST
import balti.xposed.pixelifygooglephotos.Constants.PREF_STRICTLY_CHECK_GOOGLE_PHOTOS
import balti.xposed.pixelifygooglephotos.Constants.SHARED_PREF_FILE_NAME
import de.robv.android.xposed.*
import de.robv.android.xposed.callbacks.XC_LoadPackage
class FeatureSpoofer: IXposedHookLoadPackage {
/**
* Actual class is not android.content.pm.PackageManager.
* It is an abstract class which cannot be hooked.
* Actual class found from stackoverflow:
* https://stackoverflow.com/questions/66523720/xposed-cant-hook-getinstalledapplications
*/
private val CLASS_APPLICATION_MANAGER = "android.app.ApplicationPackageManager"
/**
* Method hasSystemFeature(). Two signatures exist. We need to hook both.
* https://developer.android.com/reference/android/content/pm/PackageManager#hasSystemFeature(java.lang.String)
* https://developer.android.com/reference/android/content/pm/PackageManager#hasSystemFeature(java.lang.String,%20int)
*/
private val METHOD_HAS_SYSTEM_FEATURE = "hasSystemFeature"
/**
* Simple message to log messages in lsposed log as well as android log.
*/
private fun log(message: String){
XposedBridge.log("PixelifyGooglePhotos: $message")
Log.d("PixelifyGooglePhotos", message)
}
/**
* To read preference of user.
*/
private val pref by lazy {
XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREF_FILE_NAME).apply {
log("Preference location: ${file.canonicalPath}")
}
}
private val verboseLog: Boolean by lazy {
pref.getBoolean(PREF_ENABLE_VERBOSE_LOGS, false)
}
/**
* This is the final list of features to spoof.
* Gets the specific set of features to be enabled selected by the user.
* Default case: uses all features from "Pixel 2016" to "Pixel 2020".
*/
private val finalFeaturesToSpoof: List<String> by lazy {
val defaultFeatures = DeviceProps.defaultFeatures
val defaultFeatureLevelsName = defaultFeatures.map { it.displayName }.toSet()
val featureFlags = pref.getStringSet(PREF_SPOOF_FEATURES_LIST, defaultFeatureLevelsName)?.let { set ->
val eligibleFeatures: List<DeviceProps.Features> =
when {
set.isEmpty() -> {
log("Feature flags init: EMPTY SET")
listOf()
}
set == defaultFeatureLevelsName -> {
log("Feature flags init: DEFAULT SET")
defaultFeatures
}
else -> DeviceProps.allFeatures.filter { set.contains(it.displayName) }
}
val allFeatureFlags = ArrayList<String>(0)
eligibleFeatures.forEach {
allFeatureFlags.addAll(it.featureFlags)
}
allFeatureFlags
}?: listOf()
featureFlags.apply {
log("Pass TRUE for feature flags: $featureFlags")
}
}
/**
* Preference to override upper feature levels from custom ROMs
*/
private val overrideCustomROMLevels by lazy {
pref.getBoolean(PREF_OVERRIDE_ROM_FEATURE_LEVELS, true)
}
/**
* List of feature flags which are not present in [finalFeaturesToSpoof].
* If any feature is in this list, spoof it as not present.
* Only if preference [PREF_OVERRIDE_ROM_FEATURE_LEVELS] are enabled.
*/
private val featuresNotToSpoof: List<String> by lazy {
val allFeatureFlags = ArrayList<String>(0)
DeviceProps.allFeatures.map { it.featureFlags }.forEach {
allFeatureFlags.addAll(it)
}
allFeatureFlags.filter { it !in finalFeaturesToSpoof }.apply {
log("Pass FALSE for feature flags: $this")
}
}
/**
* If a feature needed for google photos is needed, i.e. features in [finalFeaturesToSpoof],
* then set result of hooked method [METHOD_HAS_SYSTEM_FEATURE] as `true`.
* If [PREF_OVERRIDE_ROM_FEATURE_LEVELS] is enabled, and the feature is present in [featuresNotToSpoof]
* then set result as `false`.
* Else don't set anything.
*/
private fun spoofFeatureEnquiryResultIfNeeded(param: XC_MethodHook.MethodHookParam?){
val arguments = param?.args?.toList()
var passFeatureTrue = false
var passFeatureFalse = false
arguments?.forEach {
if (it.toString() in finalFeaturesToSpoof) passFeatureTrue = true
else if (overrideCustomROMLevels){
if (it.toString() in featuresNotToSpoof) passFeatureFalse = true
}
}
if (passFeatureTrue) param?.setResult(true).apply {
if (verboseLog) log("TRUE - feature args: $arguments")
}
else if (passFeatureFalse) param?.setResult(false).apply {
if (verboseLog) log("FALSE - feature args: $arguments")
}
else {
if (verboseLog) log("NO_CHANGE - feature args: $arguments")
}
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
/**
* If user selects to never use this on any other app other than Google photos,
* then check package name and return if necessary.
*/
if (pref.getBoolean(PREF_STRICTLY_CHECK_GOOGLE_PHOTOS, true) &&
lpparam?.packageName != PACKAGE_NAME_GOOGLE_PHOTOS) return
log("Loaded FeatureSpoofer for ${lpparam?.packageName}")
/**
* Hook hasSystemFeature(String).
*/
XposedHelpers.findAndHookMethod(
CLASS_APPLICATION_MANAGER,
lpparam?.classLoader,
METHOD_HAS_SYSTEM_FEATURE, String::class.java,
object: XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
spoofFeatureEnquiryResultIfNeeded(param)
}
}
)
/**
* Hook hasSystemFeature(String, int).
*/
XposedHelpers.findAndHookMethod(
CLASS_APPLICATION_MANAGER,
lpparam?.classLoader,
METHOD_HAS_SYSTEM_FEATURE, String::class.java, Int::class.java,
object: XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
spoofFeatureEnquiryResultIfNeeded(param)
}
}
)
}
}
================================================
FILE: app/src/main/java/balti/xposed/pixelifygooglephotos/Utils.kt
================================================
package balti.xposed.pixelifygooglephotos
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import balti.xposed.pixelifygooglephotos.Constants.PREF_DEVICE_TO_SPOOF
import balti.xposed.pixelifygooglephotos.Constants.PREF_ENABLE_VERBOSE_LOGS
import balti.xposed.pixelifygooglephotos.Constants.PREF_LAST_VERSION
import balti.xposed.pixelifygooglephotos.Constants.PREF_OVERRIDE_ROM_FEATURE_LEVELS
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_ANDROID_VERSION_MANUAL
import balti.xposed.pixelifygooglephotos.Constants.PREF_SPOOF_FEATURES_LIST
import balti.xposed.pixelifygooglephotos.Constants.PREF_STRICTLY_CHECK_GOOGLE_PHOTOS
import org.json.JSONArray
import org.json.JSONObject
import java.io.BufferedWriter
import java.io.ByteArrayOutputStream
import java.io.OutputStreamWriter
/**
* Utilities class for various functions.
*/
class Utils {
/**
* Used to force close an app.
*
* Uses root to stop an application.
*
* Tried my level best to use xposed API to force stop the application,
* but it kept throwing error that XposedHelpers not found. No idea why.
*/
fun forceStopPackage(packageName: String, context: Context){
try {
Toast.makeText(context, R.string.killing_please_wait, Toast.LENGTH_SHORT).show()
Runtime.getRuntime().exec("su").apply {
BufferedWriter(OutputStreamWriter(this.outputStream)).run {
this.write("am force-stop $packageName\n")
this.write("exit\n")
this.flush()
}
}
} catch (e: Exception){
Toast.makeText(context, R.string.failed_to_stop_package, Toast.LENGTH_SHORT).show()
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", packageName, null)
}
context.startActivity(intent)
}
/*
* This could should have theoretically worked:
*
* val activityManager: ActivityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
* XposedHelpers.callMethod(activityManager, "forceStopPackage", packageName)
*/
}
/**
* Launch an app.
*/
fun openApplication(packageName: String, context: Context){
try {
val pm = context.packageManager
val launchIntent = pm.getLaunchIntentForPackage(packageName)
if (launchIntent != null) {
context.startActivity(launchIntent)
}
}
catch (e: Exception){
Toast.makeText(context, R.string.failed_to_launch_package, Toast.LENGTH_SHORT).show()
}
}
/**
* Change permissions on private data, shared_prefs directory and preferences file.
* Otherwise XSharedPreference cannot read the file.
* Solution inspired from:
* https://github.com/rovo89/XposedBridge/issues/233
* https://github.com/GravityBox/GravityBox/blob/0aec21792c218a48602a258fbb0ab1fcb1e9be0c/GravityBox/src/main/java/com/ceco/r/gravitybox/WorldReadablePrefs.java
*/
/*@SuppressLint("SetWorldReadable")
fun fixPermissions(thisPackageName: String) {
val dataDirectory = File("/data/data/$thisPackageName")
dataDirectory.apply {
setExecutable(true, false)
setReadable(true, false)
}
val sharedPrefsFolder = File(dataDirectory, "shared_prefs")
sharedPrefsFolder.apply {
if (exists()){
setExecutable(true, false)
setReadable(true, false)
}
}
val prefsFile = File(sharedPrefsFolder, "${Constants.SHARED_PREF_FILE_NAME}.xml")
prefsFile.apply {
if (exists()) setReadable(true, false)
}
}*/
/**
* Write all keys of shared preference in a file as a JSON string.
*
* @param context Activity context
* @param uri Uri of file to write to.
* Using uri as it can be used to write a file in internal cache directory,
* as well as an external location opened using [Intent.ACTION_CREATE_DOCUMENT].
* @param pref SharedPreference instance.
*/
fun writeConfigFile(context: Context, uri: Uri, pref: SharedPreferences?) {
// List of keys from shared preference which need not be copied to file.
// Or copied later like PREF_SPOOF_FEATURES_LIST.
val fieldsNotToCopy = listOf(PREF_LAST_VERSION, PREF_SPOOF_FEATURES_LIST)
val outputStream = context.contentResolver.openOutputStream(uri)
val writer = BufferedWriter(OutputStreamWriter(outputStream))
val jsonObject = JSONObject()
pref?.all?.let { allPrefs ->
for (key in allPrefs.keys){
if (key !in fieldsNotToCopy) jsonObject.put(key, allPrefs[key])
}
}
// Store PREF_SPOOF_FEATURES_LIST
pref?.getStringSet(PREF_SPOOF_FEATURES_LIST, setOf())?.let {
jsonObject.put(PREF_SPOOF_FEATURES_LIST, JSONArray(it.toTypedArray()))
}
writer.run {
write(jsonObject.toString(4))
close()
}
}
/**
* Read an exported JSON file and stores entries in shared preference.
*
* @param context Activity context
* @param uri Uri of file to read from.
* @param pref SharedPreference instance.
*/
fun readConfigFile(context: Context, uri: Uri, pref: SharedPreferences?) {
var jsonObject = JSONObject()
val baos = ByteArrayOutputStream()
val inputStream = context.contentResolver.openInputStream(uri)
inputStream?.use { input ->
baos.use { output ->
input.copyTo(output)
}
jsonObject = JSONObject(baos.toString())
}
/**
* In inbuilt function exists to convert JSONArray to List.
*/
fun convertJsonArrayToList(jsonArray: JSONArray): List<String> {
val list = ArrayList<String>()
for (i in 0 until jsonArray.length()) {
list.add(jsonArray[i].toString())
}
return list
}
/**
* Check for field and store in shared prefs.
*/
pref?.edit()?.apply {
PREF_SPOOF_FEATURES_LIST.let { key ->
jsonObject.optJSONArray(key)?.let {
putStringSet(key, convertJsonArrayToList(it).toSet())
}
}
PREF_DEVICE_TO_SPOOF.let { key ->
jsonObject.optString(key)?.let {
putString(key, it)
}
}
PREF_STRICTLY_CHECK_GOOGLE_PHOTOS.let { key ->
jsonObject.optBoolean(key, true).let {
putBoolean(key, it)
}
}
PREF_OVERRIDE_ROM_FEATURE_LEVELS.let { key ->
jsonObject.optBoolean(key, true).let {
putBoolean(key, it)
}
}
/** Advanced options */
PREF_ENABLE_VERBOSE_LOGS.let { key ->
jsonObject.optBoolean(key, true).let {
putBoolean(key, it)
}
}
PREF_SPOOF_ANDROID_VERSION_FOLLOW_DEVICE.let { key ->
jsonObject.optBoolean(key, true).let {
putBoolean(key, it)
}
}
PREF_SPOOF_ANDROID_VERSION_MANUAL.let { key ->
jsonObject.optString(key)?.let {
putString(key, it)
}
}
apply()
}
}
}
================================================
FILE: app/src/main/res/drawable/ic_export.xml
================================================
<vector android:height="30dp" android:tint="#8F8F8F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_import.xml
================================================
<vector android:height="30dp" android:tint="#8F8F8F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_info.xml
================================================
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_open.xml
================================================
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.21304688"
android:scaleY="0.21304688"
android:translateX="-0.54"
android:translateY="-0.54">
<group>
<clip-path
android:pathData="M0,0h512v512h-512z"/>
<path
android:pathData="M-8.666,-5.488h531.415v522.876h-531.415z"
android:fillColor="#182A39"/>
<path
android:pathData="M-52.692,569.148C109.224,109.431 274.689,109.451 443.637,569.148">
<aapt:attr name="android:fillColor">
<gradient
android:startY="387.07437"
android:startX="45.65521"
android:endY="239.52113"
android:endX="278.26874"
android:type="linear">
<item android:offset="0.008333334" android:color="#FF2E5EAC"/>
<item android:offset="0.9875" android:color="#FF4285F4"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M88.387,565.081C238.659,154.603 392.224,154.621 549.022,565.081">
<aapt:attr name="android:fillColor">
<gradient
android:startY="440.2372"
android:startX="251.56363"
android:endY="336.55188"
android:endX="438.53683"
android:type="linear">
<item android:offset="0" android:color="#FF195128"/>
<item android:offset="1" android:color="#FF34A853"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M276.276,170.813a40.61,39.957 0,1 0,81.219 0a40.61,39.957 0,1 0,-81.219 0z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M279.024,343.333L279.011,337.069C275.249,335.713 275.239,335.71 271.478,334.353L267.596,339.213C265.088,339.345 263.82,339.411 261.311,339.543L256.944,335.116C253.344,336.859 253.335,336.864 249.736,338.606L250.376,344.837C248.697,346.733 247.848,347.691 246.169,349.587L240.005,349.591C238.676,353.412 238.673,353.422 237.344,357.242L242.131,361.194C242.265,363.744 242.332,365.032 242.466,367.582L238.117,372.014C239.837,375.675 239.841,375.684 241.562,379.345L247.692,378.703C249.56,380.413 250.504,381.277 252.372,382.986L252.385,389.25C256.147,390.606 256.157,390.61 259.918,391.966L263.8,387.106C266.309,386.975 267.576,386.908 270.085,386.776L274.452,391.203C278.052,389.46 278.061,389.456 281.661,387.713L281.02,381.482C282.699,379.586 283.548,378.628 285.228,376.732L291.391,376.728C292.72,372.908 292.723,372.898 294.052,369.077L289.265,365.125C289.131,362.575 289.064,361.287 288.93,358.737L293.279,354.306C291.559,350.645 291.555,350.635 289.834,346.975L283.704,347.617C281.836,345.907 280.892,345.043 279.024,343.333ZM272.055,368.977C268.898,372.54 263.494,372.824 259.983,369.611C256.473,366.398 256.185,360.905 259.342,357.343C262.498,353.78 267.902,353.496 271.413,356.709C274.923,359.921 275.211,365.414 272.055,368.977Z"
android:fillColor="#FFD600"/>
<path
android:pathData="M253.958,352.416C248.139,358.986 248.67,369.15 255.144,375.074C261.617,380.998 271.618,380.473 277.438,373.904C283.257,367.334 282.726,357.17 276.252,351.246C269.779,345.321 259.778,345.846 253.958,352.416ZM275.292,371.94C270.536,377.309 262.363,377.739 257.072,372.897C251.782,368.055 251.348,359.748 256.104,354.379C260.86,349.01 269.033,348.581 274.323,353.423C279.614,358.264 280.049,366.571 275.292,371.94Z"
android:fillColor="#FBBC04"/>
<path
android:pathData="M258.269,356.361C254.586,360.518 254.923,366.951 259.019,370.699C263.115,374.448 269.444,374.116 273.127,369.959C276.81,365.801 276.473,359.369 272.377,355.62C268.28,351.871 261.952,352.203 258.269,356.361ZM270.982,367.995C268.362,370.952 263.861,371.189 260.948,368.522C258.034,365.856 257.795,361.281 260.414,358.324C263.033,355.367 267.535,355.131 270.448,357.797C273.362,360.464 273.601,365.039 270.982,367.995Z"
android:fillColor="#FBBC04"/>
<path
android:pathData="M300.153,311.884L298.525,307.928C295.799,308.075 295.792,308.075 293.067,308.222L291.873,312.331C290.323,313.084 289.54,313.464 287.99,314.218L284.088,312.586C282.266,314.648 282.261,314.654 280.439,316.717L282.455,320.484C281.885,322.131 281.597,322.963 281.027,324.611L277.136,326.259C277.285,329.029 277.285,329.036 277.434,331.806L281.479,333.026C282.222,334.602 282.598,335.398 283.341,336.975L281.741,340.938C283.774,342.792 283.779,342.797 285.812,344.652L289.516,342.609C291.138,343.191 291.957,343.485 293.579,344.067L295.206,348.023C297.932,347.876 297.939,347.876 300.664,347.729L301.859,343.62C303.408,342.867 304.191,342.486 305.741,341.733L309.643,343.365C311.465,341.302 311.47,341.297 313.292,339.234L311.276,335.466C311.846,333.819 312.135,332.987 312.705,331.34L316.595,329.692C316.446,326.922 316.446,326.915 316.297,324.145L312.252,322.925C311.509,321.349 311.133,320.552 310.39,318.976L311.99,315.013C309.957,313.159 309.952,313.154 307.919,311.299L304.215,313.342C302.594,312.76 301.774,312.466 300.153,311.884ZM302.383,329.955C301.311,333.05 297.973,334.673 294.926,333.579C291.878,332.486 290.277,329.09 291.348,325.995C292.42,322.9 295.758,321.278 298.806,322.371C301.853,323.465 303.454,326.86 302.383,329.955Z"
android:fillColor="#F44336"/>
<path
android:pathData="M286.676,324.319C284.7,330.026 287.664,336.309 293.283,338.325C298.901,340.342 305.08,337.339 307.055,331.632C309.031,325.925 306.067,319.642 300.448,317.626C294.83,315.609 288.652,318.612 286.676,324.319ZM305.193,330.964C303.579,335.628 298.529,338.082 293.937,336.434C289.346,334.786 286.923,329.651 288.538,324.987C290.153,320.323 295.202,317.869 299.794,319.517C304.385,321.165 306.808,326.3 305.193,330.964Z"
android:fillColor="#E53935"/>
<path
android:pathData="M290.417,325.661C289.167,329.273 291.043,333.249 294.598,334.525C298.154,335.801 302.064,333.901 303.314,330.29C304.564,326.678 302.688,322.702 299.133,321.426C295.577,320.15 291.668,322.05 290.417,325.661ZM301.452,329.621C300.563,332.19 297.782,333.541 295.253,332.634C292.724,331.726 291.39,328.898 292.279,326.33C293.169,323.761 295.949,322.41 298.478,323.317C301.007,324.225 302.341,327.053 301.452,329.621Z"
android:fillColor="#E53905"/>
</group>
</group>
</vector>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_margin="25dp"
android:id="@+id/root_view_for_snackbar"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_export"
android:backgroundTint="@android:color/white"
android:contentDescription="@string/export_config"
android:id="@+id/conf_export"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_import"
android:backgroundTint="@android:color/white"
android:contentDescription="@string/import_config"
android:id="@+id/conf_import"
/>
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset"
android:id="@+id/reset_settings"
/>
</LinearLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_to_spoof"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/spoofs_build_and_features"/>
</LinearLayout>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/device_spoofer_spinner"/>
</LinearLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clickable="true"
android:focusable="true"
android:paddingTop="10dp"
android:foreground="?android:attr/selectableItemBackground"
android:id="@+id/customize_feature_flags"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/customize_feature_flags"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/feature_flags_changed"
android:textColor="?android:attr/colorPrimary"
android:alpha="0.0"
android:id="@+id/feature_flags_changed"
/>
</LinearLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<androidx.appcompat.widget.SwitchCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/override_rom_feature_levels"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/override_rom_feature_levels"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/override_rom_feature_levels_desc"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<androidx.appcompat.widget.SwitchCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/spoof_only_in_google_photos"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/spoof_only_in_google_photos_switch"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/spoof_only_in_google_photos_desc"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<Space
android:layout_width="wrap_content"
android:layout_height="15dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_options"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold"
android:clickable="true"
android:focusable="true"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:foreground="?android:attr/selectableItemBackground"
android:id="@+id/advanced_options"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="15dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update_available"
android:textColor="@color/red"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium"
android:foreground="?android:attr/selectableItemBackground"
android:visibility="gone"
android:id="@+id/update_available_link"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="5dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/telegram_group"
android:textColor="?android:attr/colorPrimary"
android:clickable="true"
android:focusable="true"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceMedium"
android:foreground="?android:attr/selectableItemBackground"
android:id="@+id/telegram_group"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/force_stop_google_photos"
android:elevation="3dp"
android:id="@+id/force_stop_google_photos"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/force_stop_google_photos"
android:layout_alignTop="@id/force_stop_google_photos"
android:layout_alignBottom="@id/force_stop_google_photos"
android:backgroundTint="?android:attr/colorPrimary"
android:contentDescription="@string/open_google_photos"
android:src="@drawable/ic_open"
android:elevation="3dp"
android:id="@+id/open_google_photos"
/>
</RelativeLayout>
</LinearLayout>
</ScrollView>
================================================
FILE: app/src/main/res/layout/advanced_options_activity.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_margin="25dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_verbose_logging"
android:id="@+id/verbose_logging"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_verbose_logging_desc"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/spoof_android_version"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="10dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/this_might_cause_crash"
android:textColor="@color/red"
android:textStyle="italic"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/spoof_android_version_desc"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="10dp"/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/android_version_radio_group"
>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_spoof_android_version"
android:id="@+id/dont_spoof_android_version"
/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/follow_spoof_device_version"
android:id="@+id/follow_spoof_device_version"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginStart="32dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/device_name_label"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" - "
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/colorAccent"
android:id="@+id/device_android_version"
/>
</LinearLayout>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/manually_set_android_version"
android:id="@+id/manually_set_android_version"
/>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:visibility="gone"
android:id="@+id/android_version_spinner"
/>
</RadioGroup>
<Space
android:layout_width="wrap_content"
android:layout_height="25dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_gravity="center_horizontal"
android:id="@+id/save_advanced_option"
/>
</LinearLayout>
</ScrollView>
================================================
FILE: app/src/main/res/layout/feature_customize.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_margin="25dp"
android:orientation="vertical"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:id="@+id/feature_checkbox_holder"
>
</LinearLayout>
</ScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_gravity="center_horizontal"
android:id="@+id/save_features"
/>
</LinearLayout>
================================================
FILE: app/src/main/res/menu/menu_activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_changelog"
android:icon="@drawable/ic_info"
app:showAsAction="ifRoom"
android:title="@string/changelog"
/>
</menu>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="red">#C62828</color>
</resources>
================================================
FILE: app/src/main/res/values/module_scope.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="module_scope">
<item>com.google.android.apps.photos</item>
</string-array>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
<!--Things to update in new versions:
build.gradle: versionCode
build.gradle: versionName
update_info.json
changelog
-->
<string name="app_name">Pixelify GPhotos</string>
<!--activity_main.xml-->
<string name="reset">Reset</string>
<string name="device_to_spoof">Device to spoof</string>
<string name="spoofs_build_and_features">Spoofs build.prop and feature flags.</string>
<string name="customize_feature_flags">Customize feature flags.</string>
<string name="feature_flags_changed">Feature flags changed!</string>
<string name="spoof_only_in_google_photos">Make sure to spoof only in Google Photos.</string>
<string name="spoof_only_in_google_photos_desc">In LSPosed, the module is by default only activated for Google Photos.
If you want to use it in other apps, then keep this toggle <b>OFF</b>.</string>
<string name="override_rom_feature_levels">Override ROM feature levels.</string>
<string name="override_rom_feature_levels_desc">Pass false to hooked method for higher feature levels present in ROM
if user selects a lower Pixel feature level. <b>(Recommended)</b></string>
<string name="force_stop_google_photos">Force stop\nGoogle Photos</string>
<string name="module_not_enabled">
\n
Looks like the this app module is not enabled.\n
Please enable it in LSPosed / EdXposed, then reboot.\n
If already enabled, then just reboot. If that does not work, kindly uninstall and reinstall.
</string>
<string name="close">Close</string>
<string name="open_google_photos">Open Google Photos</string>
<string name="telegram_group">Telegram group!</string>
<string name="update_available">App update available!</string>
<!--changelog-->
<string name="version_head">Version 4.1</string>
<string name="version_desc">
\n
# In light of recent event of Vanced project getting shut down because of using Youtube logo,
this app\'s logo has been updated to avoid any legal conflict.\n
</string>
<!--menu_activity_main.xml-->
<string name="changelog">Change log</string>
<!--ActivityMain.xml-->
<string name="please_force_stop_google_photos">Please force stop Google Photos to take effect.</string>
<string name="export_config">Export configuration</string>
<string name="export_config_desc">Save or share your settings of this module with others.\n
\"Save\" will prompt to select a location to export the settings as a JSON file.
</string>
<string name="export_complete">Export complete!</string>
<string name="share">Share</string>
<string name="select_a_location">Select a location to save.</string>
<string name="share_config_file">Share configuration file</string>
<string name="import_config">Import configuration</string>
<string name="import_config_desc">This will open a system window to select the configuration file.</string>
<string name="share_error">Share error!</string>
<string name="read_error">Read error! Make sure to select a proper file!</string>
<string name="import_complete">Import complete!</string>
<string name="advanced_options">Advanced options</string>
<!--feature_customize.xml-->
<string name="save">Save</string>
<!--advanced_options_activity.xml-->
<string name="enable_verbose_logging">Enable verbose logging</string>
<string name="enable_verbose_logging_desc">More debug information in LSPosed / EdXposed logs and Android logcat.\n
To share logs, open LSPosed / EdXposed -> Logs -> top Save icon. Then share the created file.
</string>
<string name="spoof_android_version">Spoof android version.</string>
<string name="spoof_android_version_desc">Do not set an Android version greater than your current version.\n
Example, if your Android version is <b>Pie</b>, you can set <b>Nougat / Oreo / Pie</b>, but not Q / R / S.
</string>
<string name="this_might_cause_crash">This might cause Google Photos to immediately crash!</string>
<string name="dont_spoof_android_version">Don\'t spoof Android version <b>(Recommended)</b></string>
<string name="follow_spoof_device_version">Follow spoof device Android version.</string>
<string name="manually_set_android_version">Manually set Android version.</string>
<!--Utils.kt-->
<string name="killing_please_wait">Killing… please wait.</string>
<string name="failed_to_stop_package">Failed to stop package. Please stop manually.</string>
<string name="failed_to_launch_package">Failed to launch. Please start manually.</string>
</resources>
================================================
FILE: app/src/main/res/values/themes.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PixelifyGooglePhotos" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="colorAccent">@color/purple_500</item>
</style>
</resources>
================================================
FILE: app/src/main/res/values-night/themes.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PixelifyGooglePhotos" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="colorAccent">@color/purple_200</item>
</style>
</resources>
================================================
FILE: app/src/main/res/values-zh-rTW/strings.xml
================================================
<resources>
<!--Things to update in new versions:
build.gradle: versionCode
build.gradle: versionName
update_info.json
changelog
-->
<string name="app_name">Pixelify Google Photos</string>
<!--activity_main.xml-->
<string name="reset">還原</string>
<string name="device_to_spoof">裝置模擬</string>
<string name="spoofs_build_and_features">修改 build.prop 下的裝置參數</string>
<string name="customize_feature_flags">自定義功能參數</string>
<string name="feature_flags_changed">功能參數已更改!</string>
<string name="spoof_only_in_google_photos">確保僅對 Google 相簿進行 HOOK</string>
<string name="spoof_only_in_google_photos_desc">因 LSPosed 中僅使用白名單,預設該模組僅針對 Google 相簿啟用,
如果您想在其他應用中使用它,請保持此開關 <b>關閉</b>.</string>
<string name="override_rom_feature_levels">覆蓋 ROM 自訂功能</string>
<string name="override_rom_feature_levels_desc">如果使用者選擇較低版本的 Pixel,
將避免部分 ROM 內含有模擬 Pixel 的功能影響 Pixelify Google Photos <b>(推薦啟用)</b></string>
<string name="force_stop_google_photos">強制停止\nGoogle 相簿</string>
<string name="module_not_enabled">
\n
此模組似乎未啟用。\n
請在 LSPosed / EdXposed 中啟用它,然後重新啟動。\n
如果已啟用,可能需強制強制停止 Pixelify Google Photos 後重啟。\n
如果沒用,請移除並重新安裝。
</string>
<string name="close">關閉</string>
<string name="open_google_photos">開啟 Google 相簿</string>
<string name="telegram_group">Telegram 討論組!</string>
<string name="update_available">有最新的更新可用!</string>
<!--menu_activity_main.xml-->
<string name="changelog">更改日誌</string>
<!--ActivityMain.xml-->
<string name="please_force_stop_google_photos">請強制停止 Google 相簿以使其生效。</string>
<string name="export_config">匯出配置</string>
<string name="export_config_desc">備份或與他人分享此模組的設定。\n
點選 \"儲存\" 後將提示選擇一個位置以將設定匯出為 JSON 檔案。
</string>
<string name="export_complete">匯出成功!</string>
<string name="share">分享</string>
<string name="select_a_location">選擇要儲存的位置</string>
<string name="share_config_file">共享設定檔</string>
<string name="import_config">匯入設定檔</string>
<string name="import_config_desc">點選後請選擇要匯入的設定檔</string>
<string name="share_error">分享錯誤紀錄!</string>
<string name="read_error">讀取錯誤! 請確認是否有選擇適合的檔案!</string>
<string name="import_complete">匯入成功!</string>
<string name="advanced_options">進階設定</string>
<!--feature_customize.xml-->
<string name="save">儲存</string>
<!--advanced_options_activity.xml-->
<string name="enable_verbose_logging">開啟詳細日誌記錄</string>
<string name="enable_verbose_logging_desc">LSPosed / EdXposed 日誌和 Android logcat 中的的除錯資訊。\n
如果要匯出日誌,請開啟 LSPosed / EdXposed -> 日誌 -> 頂部儲存圖示。然後分享儲存後的檔案或壓縮檔。
</string>
<string name="spoof_android_version">模擬 Android 版本</string>
<string name="spoof_android_version_desc">不要設定高於您當前版本 Android 的版本。\n
例如,如果您的 Android 版本是 <b>Pie</b>,那您可以設定<b>Nougat / Oreo / Pie</b>,而不能設定以下的版本 Q / R / S.
</string>
<string name="this_might_cause_crash">這可能會導致 Google 相簿立即崩潰!</string>
<string name="dont_spoof_android_version">不要模擬 Android 的版本 <b>(推薦)</b></string>
<string name="follow_spoof_device_version">與選擇模擬的裝置版本一致</string>
<string name="manually_set_android_version">手動設定 Android 的版本</string>
<!--Utils.kt-->
<string name="killing_please_wait">強制停止中…請稍候。</string>
<string name="failed_to_stop_package">無法自動強制停止。請手動強制停止。</string>
<string name="failed_to_launch_package">無法自動啟動。請手動啟動。</string>
</resources>
================================================
FILE: app/src/main/res/xml/provider_paths.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--Guide at: https://infinum.com/the-capsized-eight/share-files-using-fileprovider-->
<paths>
<cache-path name="cache" path="/" />
</paths>
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Dec 01 22:54:51 IST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
rootProject.name = "Pixelify Google Photos"
include ':app'
================================================
FILE: update_info.json
================================================
{
"latest_version_code": 5
}
gitextract_63__q8nw/ ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── .name │ ├── compiler.xml │ └── misc.xml ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── xposed_init │ ├── java/ │ │ └── balti/ │ │ └── xposed/ │ │ └── pixelifygooglephotos/ │ │ ├── ActivityMain.kt │ │ ├── AdvancedOptionsActivity.kt │ │ ├── Constants.kt │ │ ├── DeviceProps.kt │ │ ├── DeviceSpoofer.kt │ │ ├── FeatureCustomize.kt │ │ ├── FeatureSpoofer.kt │ │ └── Utils.kt │ └── res/ │ ├── drawable/ │ │ ├── ic_export.xml │ │ ├── ic_import.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_open.xml │ ├── drawable-v24/ │ │ └── ic_launcher_foreground.xml │ ├── layout/ │ │ ├── activity_main.xml │ │ ├── advanced_options_activity.xml │ │ └── feature_customize.xml │ ├── menu/ │ │ └── menu_activity_main.xml │ ├── mipmap-anydpi-v26/ │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── module_scope.xml │ │ ├── strings.xml │ │ └── themes.xml │ ├── values-night/ │ │ └── themes.xml │ ├── values-zh-rTW/ │ │ └── strings.xml │ └── xml/ │ └── provider_paths.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── update_info.json
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (129K chars).
[
{
"path": ".gitignore",
"chars": 1712,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": ".idea/.gitignore",
"chars": 47,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n"
},
{
"path": ".idea/.name",
"chars": 22,
"preview": "Pixelify Google Photos"
},
{
"path": ".idea/compiler.xml",
"chars": 169,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"CompilerConfiguration\">\n <bytecodeTar"
},
{
"path": ".idea/misc.xml",
"chars": 2325,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"DesignSurface\">\n <option name=\"filePa"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2021 BaltiApps\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 1988,
"preview": "# Pixelify-Google-Photos\nLSPosed / EdXposed module to add Google Pixel features on Google Photos for any device. \nTeste"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 954,
"preview": "plugins {\n id 'com.android.application'\n id 'kotlin-android'\n}\n\nandroid {\n compileSdk 31\n\n defaultConfig {\n "
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 2141,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "app/src/main/assets/xposed_init",
"chars": 96,
"preview": "balti.xposed.pixelifygooglephotos.FeatureSpoofer\nbalti.xposed.pixelifygooglephotos.DeviceSpoofer"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/ActivityMain.kt",
"chars": 18710,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.grap"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/AdvancedOptionsActivity.kt",
"chars": 5253,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.widget.*\n"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/Constants.kt",
"chars": 1350,
"preview": "package balti.xposed.pixelifygooglephotos\n\nobject Constants {\n\n val PACKAGE_NAME_GOOGLE_PHOTOS = \"com.google.android."
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/DeviceProps.kt",
"chars": 11813,
"preview": "package balti.xposed.pixelifygooglephotos\n\n/**\n * Build values taken from:\n * Pixel 6:\n * https://github.com/DotOS/andro"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/DeviceSpoofer.kt",
"chars": 4044,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.os.Build\nimport android.util.Log\nimport balti.xposed.pixelifyg"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/FeatureCustomize.kt",
"chars": 2509,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.widget.Bu"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/FeatureSpoofer.kt",
"chars": 6972,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.util.Log\nimport balti.xposed.pixelifygooglephotos.Constants.PA"
},
{
"path": "app/src/main/java/balti/xposed/pixelifygooglephotos/Utils.kt",
"chars": 7922,
"preview": "package balti.xposed.pixelifygooglephotos\n\nimport android.content.Context\nimport android.content.Intent\nimport android.c"
},
{
"path": "app/src/main/res/drawable/ic_export.xml",
"chars": 322,
"preview": "<vector android:height=\"30dp\" android:tint=\"#8F8F8F\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_import.xml",
"chars": 323,
"preview": "<vector android:height=\"30dp\" android:tint=\"#8F8F8F\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_info.xml",
"chars": 383,
"preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 4867,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n android:height=\"108dp\"\n android:width=\"108dp\"\n android:viewport"
},
{
"path": "app/src/main/res/drawable/ic_open.xml",
"chars": 418,
"preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 6504,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 9071,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/layout/advanced_options_activity.xml",
"chars": 4848,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/layout/feature_customize.xml",
"chars": 1063,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "app/src/main/res/menu/menu_activity_main.xml",
"chars": 341,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"h"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 270,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 270,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 417,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"purple_200\">#FFBB86FC</color>\n <color name=\"purpl"
},
{
"path": "app/src/main/res/values/module_scope.xml",
"chars": 174,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string-array name=\"module_scope\">\n <item>com.google.andro"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 4752,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\"\n tools:ignore=\"MissingTranslation\">\n\n <!--Things to upda"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 900,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.P"
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 900,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.P"
},
{
"path": "app/src/main/res/values-zh-rTW/strings.xml",
"chars": 3439,
"preview": "<resources>\n\n <!--Things to update in new versions:\n\n build.gradle: versionCode\n build.gradle: versionName\n\n "
},
{
"path": "app/src/main/res/xml/provider_paths.xml",
"chars": 183,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--Guide at: https://infinum.com/the-capsized-eight/share-files-using-fileprovid"
},
{
"path": "build.gradle",
"chars": 527,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n re"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Wed Dec 01 22:54:51 IST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributio"
},
{
"path": "gradle.properties",
"chars": 1184,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "settings.gradle",
"chars": 297,
"preview": "dependencyResolutionManagement {\n repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n repositories {\n "
},
{
"path": "update_info.json",
"chars": 30,
"preview": "{\n\t\"latest_version_code\": 5\n}\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the Xposed-Modules-Repo/balti.xposed.pixelifygooglephotos GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (117.2 KB), approximately 30.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.