Merge pull request #26 from threethan/main

Add Background Audio, Update UI
This commit is contained in:
Bastian 2022-02-07 21:53:46 +01:00 committed by GitHub
commit abfd00ac51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1329 additions and 42 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="15" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="1WMHH834Y71103" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-02-05T10:56:11.381491200Z" />
</component>
</project>

View File

@ -1,6 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable-v24/buttonbg.xml" value="0.1925" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2445" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/buttonbg.xml" value="0.492" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/buttonbgfocus.xml" value="0.1925" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/buttonbgx.xml" value="0.2855" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/hover.xml" value="0.282" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/ic_launcher_background.xml" value="0.2445" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/drawable/windowbg.xml" value="0.2995" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/layout/activity_main.xml" value="0.22" />
<entry key="..\:/Portable Apps/Programming/Oculess/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.216" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_15_PREVIEW" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -5,6 +5,7 @@
- (Fully) Log Out OF Oculus / FaceBook account (aka. Disable Oculus Companion)
- Disable telemetry
- Disable Updates
- (New) Enable Audio in Background
## Tutorial
@ -52,5 +53,14 @@ Only use for to [disable telemetry](https://github.com/basti564/Oculess#disable-
5. Click “REMOVE ACCOUNT”
(Your accounts should return after like 5 minutes or a restart)
### Enable Background Audio (temporary)
1. Follow steps 1-3 of "Disable Telemetry" to set device owner. This only needs to be done once.
2. Click on "ENABLE BACKGROUND AUDIO"
Apps will now be allowed to play audio in the background, and record audio as if they have been given microphone access.
Music will be paused when a game is quit. If this happens, you will have to reopen the music app and click play.
Note that apps might still be killed. You may need to reopen apps or click the button again if oculess is killed.
Setting applies to all apps until the next reboot.
## Screenshot
![Screenshot](https://user-images.githubusercontent.com/34898868/135829932-f043a990-3fdc-433e-8eb4-e6f34d997e52.png)
![Screenshot](https://user-images.githubusercontent.com/12588584/152667664-40db8b5b-1e93-4518-836f-e1de3782a07a.jpg)

View File

@ -11,8 +11,8 @@ android {
applicationId "com.bos.oculess"
minSdkVersion 23
targetSdkVersion 30
versionCode 3
versionName "1.2"
versionCode 5
versionName "1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -33,7 +33,6 @@ android {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'

21
app/oculess.svg Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Horizontal" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 351.5 351.6"
style="enable-background:new 0 0 351.5 351.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1C1E20;}
</style>
<g>
<path class="st0" d="M312.2,93.7c-0.7-0.5-1.4-1.1-2.1-1.6l-38.6,60.3c5.8,6.3,9.1,14.6,9.1,23.4c0.1,11.5-5.5,22.1-15,28.6
c-4.7,3.3-9.9,5.2-15.4,6.1c-5.4,0.8-11,0.7-16.5,0.7l-45.4,70.8h35.1c6.7,0,13.4,0,20.1-0.1c9.1-0.3,17.8-1.3,26.7-3.4
c15.4-3.7,29.6-10.6,41.9-20.5c24.9-20,39.3-50.1,39.3-82.1C351.5,143.8,337.1,113.7,312.2,93.7z"/>
<path class="st0" d="M108.9,281.8l45.3-70.6h-36c-5.7,0-11.2,0.1-16.7-0.7c-5.7-0.8-10.8-2.8-15.4-6.1c-9.3-6.5-15-17.1-15-28.6
c0-11.5,5.7-22.1,15-28.6c4.7-3.3,9.9-5.2,15.4-6.1c5.5-0.8,11.2-0.7,16.7-0.7h81.4l45.3-70.6c-0.5,0-0.9,0-1.4-0.1
c-6.7-0.3-13.4-0.1-20.1-0.1h-95.2c-6.7,0-13.4,0-20.1,0.1c-9.1,0.3-17.8,1.3-26.7,3.4c-15.4,3.7-29.6,10.6-41.9,20.5
C14.4,113.7,0,143.8,0,175.8c0,32,14.4,62.1,39.3,82.1c12.3,9.9,26.6,16.8,42,20.5c8.8,2.1,17.7,3.1,26.7,3.4
C108.4,281.8,108.6,281.8,108.9,281.8z"/>
</g>
<rect x="120.2" y="-0.2" transform="matrix(0.8418 0.5398 -0.5398 0.8418 119.2303 -55.1826)" class="st0" width="67" height="352"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -9,6 +9,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@mipmap/ic_banner"
android:supportsRtl="true"
android:theme="@style/Theme.Oculess">
<receiver

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
class DevAdminReceiver : DeviceAdminReceiver() {
private val TAG = "DeviceAdminReceiver"

View File

@ -1,21 +1,32 @@
package com.bos.oculess
import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.WindowManager
import android.widget.Button
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.bos.oculess.util.AppOpsUtil
import kotlin.concurrent.fixedRateTimer
import android.os.Handler as Handler
class MainActivity : AppCompatActivity() {
var audioApps: Array<String>? = null
@SuppressLint("QueryPermissionsNeeded")
@RequiresApi(Build.VERSION_CODES.P)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -36,6 +47,7 @@ class MainActivity : AppCompatActivity() {
val viewAccountsBtn = findViewById<Button>(R.id.viewAccountsBtn)
val viewOtaBtn = findViewById<Button>(R.id.viewOtaBtn)
val viewTelemetryBtn = findViewById<Button>(R.id.viewTelemetryBtn)
val viewPermissionsBtn = findViewById<Button>(R.id.viewPermissionsBtn)
val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
@ -76,6 +88,38 @@ class MainActivity : AppCompatActivity() {
}
}
viewPermissionsBtn.setOnClickListener {
if (dpm.isDeviceOwnerApp(packageName)) {
updateAudioPackages()
// Set recurring task (every 2s)
val handler = Handler()
val run = object : Runnable {
override fun run() {
handler.postDelayed(this, 2000)
enableBackgroundAudio()
}
}
handler.post(run)
viewPermissionsBtn.isEnabled = false
} else {
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.title))
builder.setMessage(getString(R.string.message2))
builder.setPositiveButton(
getString(R.string.ok)
) { dialog, _ ->
dialog.dismiss()
}
val alertDialog: AlertDialog = builder.create()
alertDialog.show()
alertDialog.window!!.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
}
viewAdminsBtn.setOnClickListener {
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.title))
@ -213,4 +257,27 @@ class MainActivity : AppCompatActivity() {
}
}
}
}
@RequiresApi(Build.VERSION_CODES.P)
fun enableBackgroundAudio() {
for (app in audioApps!!) {
val info: ApplicationInfo = applicationContext.packageManager.getApplicationInfo(app, 0)
// Allow background audio playback
AppOpsUtil.setMode(applicationContext, 28, info.uid, app, AppOpsManager.MODE_ALLOWED)
// Allow background audio recording (app must have recording permission for this to do anything)
AppOpsUtil.setMode(applicationContext, 27, info.uid, app, AppOpsManager.MODE_ALLOWED)
}
}
fun updateAudioPackages() {
/* Get All Installed Packages for Audio */
//getInstalledPackages longer works properly in android 11, but quest is on android 10 so it's fine
val packageinfos = applicationContext.packageManager.getInstalledPackages(0)
val packageNames = arrayListOf<String>()
for (packageinfo in packageinfos) {
packageNames.add(packageinfo.packageName)
}
audioApps = packageNames.toTypedArray()
}
}

View File

@ -0,0 +1,89 @@
package com.bos.oculess.util;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.RequiresApi;
import com.bos.oculess.util.Hack;
/**
* @author heruoxin @ CatchingNow Inc.
* @since 2019-03-31
*/
public class AppOpsUtil {
private static AppOpsManager sManager;
@RequiresApi(api = Build.VERSION_CODES.P)
public static void setMode(Context context, int opCode, int uid, String packageName, int mode) {
if (sManager == null) {
sManager = context.getSystemService(AppOpsManager.class);
}
Hack.into(AppOpsManager.class)
.method("setMode")
.returning(void.class)
.withParams(int.class, int.class, String.class, int.class)
.invoke(opCode, uid, packageName, mode)
.on(sManager);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void startWatchingMode(Context context, int opCode, String packageName, int flags, final AppOpsManager.OnOpChangedListener callback) {
if (sManager == null) {
sManager = context.getSystemService(AppOpsManager.class);
}
Hack.into(AppOpsManager.class)
.method("startWatchingMode")
.returning(void.class)
.withParams(int.class, String.class, int.class, AppOpsManager.OnOpChangedListener.class)
.invoke(opCode, packageName, flags, callback)
.on(sManager);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void setUidMode(Context context, int opCode, int uid, int mode) {
if (sManager == null) {
sManager = context.getSystemService(AppOpsManager.class);
}
Hack.into(AppOpsManager.class)
.method("setUidMode")
.returning(void.class)
.withParams(int.class, int.class, int.class)
.invoke(opCode, uid, mode)
.on(sManager);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public static void resetAllModes(int userId, String packageName) throws RemoteException {
IBinder binder = Hack.into("android.os.ServiceManager")
.staticMethod("getService")
.returning(IBinder.class)
.withParams(String.class)
.invoke(Context.APP_OPS_SERVICE)
.statically();
Object appopsService;
try {
appopsService = Hack.into("com.android.internal.app.IAppOpsService$Stub")
.staticMethod("asInterface")
.returning(Class.forName("com.android.internal.app.IAppOpsService"))
.withParams(IBinder.class)
.invoke(binder)
.statically();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Hack.into("com.android.internal.app.IAppOpsService")
.method("resetAllModes")
.returning(void.class)
.throwing(RemoteException.class)
.withParams(int.class, String.class)
.invoke(userId, packageName)
.on(appopsService);
}
}

View File

@ -0,0 +1,861 @@
package com.bos.oculess.util;
import android.util.Log;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Comparator;
/**
* Java reflection helper optimized for hacking non-public APIs.
* The core design philosophy behind is compile-time consistency enforcement.
*
* It's suggested to declare all hacks in a centralized point, typically as static fields in a class.
* Then call it during application initialization, thus they are verified all together in an early stage.
* If any assertion failed, a fall-back strategy is suggested.
*
* <p>https://gist.github.com/oasisfeng/75d3774ca5441372f049818de4d52605
*
* @see Demo
*
* @author Oasis
*/
@SuppressWarnings({"Convert2Lambda", "WeakerAccess", "unused"})
public class Hack {
public static Class<?> ANY_TYPE = $.class; private static class $ {}
public static class AssertionException extends Throwable {
private Class<?> mClass;
private Field mHackedField;
private Method mHackedMethod;
private String mHackedFieldName;
private String mHackedMethodName;
private Class<?>[] mParamTypes;
AssertionException(final String e) { super(e); }
AssertionException(final Exception e) { super(e); }
@Override public String toString() {
return getCause() != null ? getClass().getName() + ": " + getCause() : super.toString();
}
public String getDebugInfo() {
final StringBuilder info = new StringBuilder(getCause() != null ? getCause().toString() : super.toString());
final Throwable cause = getCause();
if (cause instanceof NoSuchMethodException) {
info.append(" Potential candidates:");
final int initial_length = info.length();
final String name = getHackedMethodName();
if (name != null) {
for (final Method method : getHackedClass().getDeclaredMethods())
if (method.getName().equals(name)) // Exact name match
info.append(' ').append(method);
if (info.length() == initial_length)
for (final Method method : getHackedClass().getDeclaredMethods())
if (method.getName().startsWith(name)) // Name prefix match
info.append(' ').append(method);
if (info.length() == initial_length)
for (final Method method : getHackedClass().getDeclaredMethods())
if (! method.getName().startsWith("-")) // Dump all but generated methods
info.append(' ').append(method);
} else for (final Constructor<?> constructor : getHackedClass().getDeclaredConstructors())
info.append(' ').append(constructor);
} else if (cause instanceof NoSuchFieldException) {
info.append(" Potential candidates:");
final int initial_length = info.length();
final String name = getHackedFieldName();
final Field[] fields = getHackedClass().getDeclaredFields();
for (final Field field : fields)
if (field.getName().equals(name)) // Exact name match
info.append(' ').append(field);
if (info.length() == initial_length) for (final Field field : fields)
if (field.getName().startsWith(name)) // Name prefix match
info.append(' ').append(field);
if (info.length() == initial_length) for (final Field field : fields)
if (! field.getName().startsWith("$")) // Dump all but generated fields
info.append(' ').append(field);
}
return info.toString();
}
public Class<?> getHackedClass() {
return mClass;
}
AssertionException setHackedClass(final Class<?> hacked_class) {
mClass = hacked_class; return this;
}
public Method getHackedMethod() {
return mHackedMethod;
}
AssertionException setHackedMethod(final Method method) {
mHackedMethod = method;
return this;
}
public String getHackedMethodName() {
return mHackedMethodName;
}
AssertionException setHackedMethodName(final String method) {
mHackedMethodName = method;
return this;
}
public Class<?>[] getParamTypes() { return mParamTypes; }
AssertionException setParamTypes(final Class<?>[] param_types) {
mParamTypes = param_types;
return this;
}
public Field getHackedField() {
return mHackedField;
}
AssertionException setHackedField(final Field field) {
mHackedField = field;
return this;
}
public String getHackedFieldName() {
return mHackedFieldName;
}
AssertionException setHackedFieldName(final String field) {
mHackedFieldName = field;
return this;
}
private static final long serialVersionUID = 1L;
}
/** Placeholder for unchecked exception */
public class Unchecked extends RuntimeException {}
/** Use {@link Hack#setAssertionFailureHandler(AssertionFailureHandler) } to set the global handler */
public interface AssertionFailureHandler {
void onAssertionFailure(AssertionException failure);
}
public static class FieldToHack<C> {
protected @Nullable
<T> Field findField(final @Nullable Class<T> type) {
if (mClass == ANY_TYPE) return null; // AnyType as a internal indicator for class not found.
Field field = null;
try {
field = mClass.getDeclaredField(mName);
if (Modifier.isStatic(mModifiers) != Modifier.isStatic(field.getModifiers())) {
fail(new AssertionException(field + (Modifier.isStatic(mModifiers) ? " is not static" : " is static")).setHackedFieldName(mName));
field = null;
} else if (mModifiers > 0 && (field.getModifiers() & mModifiers) != mModifiers) {
fail(new AssertionException(field + " does not match modifiers: " + mModifiers).setHackedFieldName(mName));
field = null;
} else if (! field.isAccessible()) field.setAccessible(true);
} catch (final NoSuchFieldException e) {
final AssertionException hae = new AssertionException(e);
hae.setHackedClass(mClass);
hae.setHackedFieldName(mName);
fail(hae);
}
if (type != null && field != null && ! type.isAssignableFrom(field.getType()))
fail(new AssertionException(new ClassCastException(field + " is not of type " + type)).setHackedField(field));
return field;
}
/** @param modifiers the modifiers this field must have */
protected FieldToHack(final Class<C> clazz, final String name, final int modifiers) {
mClass = clazz;
mName = name;
mModifiers = modifiers;
}
protected final Class<C> mClass;
protected final String mName;
protected final int mModifiers;
}
public static class MemberFieldToHack<C> extends FieldToHack<C> {
/** Assert the field type. */
public @Nullable <T> HackedField<C, T> ofType(final Class<T> type) {
return ofType(type, false, null);
}
public @Nullable <T> HackedField<C, T> ofType(final String type_name) {
try { //noinspection unchecked
return ofType((Class<T>) Class.forName(type_name, false, mClass.getClassLoader()));
} catch (final ClassNotFoundException e) {
fail(new AssertionException(e));
return null;
}
}
public @NonNull
HackedField<C, Byte> fallbackTo(final byte value) { //noinspection ConstantConditions
return ofType(byte.class, true, value);
}
public @NonNull HackedField<C, Character> fallbackTo(final char value) { //noinspection ConstantConditions
return ofType(char.class, true, value);
}
public @NonNull HackedField<C, Short> fallbackTo(final short value) { //noinspection ConstantConditions
return ofType(short.class, true, value);
}
public @NonNull HackedField<C, Integer> fallbackTo(final int value) { //noinspection ConstantConditions
return ofType(int.class, true, value);
}
public @NonNull HackedField<C, Long> fallbackTo(final long value) { //noinspection ConstantConditions
return ofType(long.class, true, value);
}
public @NonNull HackedField<C, Boolean> fallbackTo(final boolean value) { //noinspection ConstantConditions
return ofType(boolean.class, true, value);
}
public @NonNull HackedField<C, Float> fallbackTo(final float value) { //noinspection ConstantConditions
return ofType(float.class, true, value);
}
public @NonNull HackedField<C, Double> fallbackTo(final double value) { //noinspection ConstantConditions
return ofType(double.class, true, value);
}
/** Fallback to the given value if this field is unavailable at runtime */
public @NonNull <T> HackedField<C, T> fallbackTo(final T value) {
@SuppressWarnings("unchecked") final Class<T> type = value == null ? null : (Class<T>) value.getClass();
//noinspection ConstantConditions
return ofType(type, true, value);
}
private <T> HackedField<C, T> ofType(final Class<T> type, final boolean fallback, final T fallback_value) {
final Field field = findField(type);
return field != null ? new HackedFieldImpl<C, T>(field) : fallback ? new FallbackField<C, T>(type, fallback_value) : null;
}
/** @param modifiers the modifiers this field must have */
private MemberFieldToHack(final Class<C> clazz, final String name, final int modifiers) {
super(clazz, name, modifiers);
}
}
public static class StaticFieldToHack<C> extends FieldToHack<C> {
/** Assert the field type. */
public @Nullable <T> HackedTargetField<T> ofType(final Class<T> type) {
return ofType(type, false, null);
}
public @Nullable <T> HackedTargetField<T> ofType(final String type_name) {
try { //noinspection unchecked
return ofType((Class<T>) Class.forName(type_name, false, mClass.getClassLoader()));
} catch (final ClassNotFoundException e) {
fail(new AssertionException(e));
return null;
}
}
/** Fallback to the given value if this field is unavailable at runtime */
public @NonNull <T> HackedTargetField<T> fallbackTo(final T value) {
@SuppressWarnings("unchecked") final Class<T> type = value == null ? null : (Class<T>) value.getClass();
//noinspection ConstantConditions
return ofType(type, true, value);
}
private <T> HackedTargetField<T> ofType(final Class<T> type, final boolean fallback, final T fallback_value) {
final Field field = findField(type);
return field != null ? new HackedFieldImpl<C, T>(field).onTarget(null) : fallback ? new FallbackField<C, T>(type, fallback_value) : null;
}
/** @param modifiers the modifiers this field must have */
private StaticFieldToHack(final Class<C> clazz, final String name, final int modifiers) {
super(clazz, name, modifiers);
}
}
public interface HackedField<C, T> {
T get(C instance);
void set(C instance, T value);
HackedTargetField<T> on(C target);
Class<T> getType();
boolean isAbsent();
}
public interface HackedTargetField<T> {
T get();
void set(T value);
Class<T> getType();
boolean isAbsent();
}
private static class HackedFieldImpl<C, T> implements HackedField<C, T> {
@Override public HackedTargetFieldImpl<T> on(final C target) {
if (target == null) throw new IllegalArgumentException("target is null");
return onTarget(target);
}
private HackedTargetFieldImpl<T> onTarget(final @Nullable C target) { return new HackedTargetFieldImpl<>(mField, target); }
/** Get current value of this field */
@Override public T get(final C instance) {
try {
@SuppressWarnings("unchecked") final T value = (T) mField.get(instance);
return value;
} catch (final IllegalAccessException e) { return null; } // Should never happen
}
/**
* Set value of this field
*
* <p>No type enforced here since most type mismatch can be easily tested and exposed early.</p>
*/
@Override public void set(final C instance, final T value) {
try {
mField.set(instance, value);
} catch (final IllegalAccessException ignored) {} // Should never happen
}
@Override @SuppressWarnings("unchecked") public @Nullable Class<T> getType() {
return (Class<T>) mField.getType();
}
@Override public boolean isAbsent() { return false; }
HackedFieldImpl(final @NonNull Field field) { mField = field; }
public @Nullable Field getField() { return mField; }
private final @NonNull Field mField;
}
private static class FallbackField<C, T> implements HackedField<C, T>, HackedTargetField<T> {
@Override public T get(final C instance) { return mValue; }
@Override public void set(final C instance, final T value) {}
@Override public T get() { return mValue; }
@Override public void set(final T value) {}
@Override public HackedTargetField<T> on(final C target) { return this; }
@Override public Class<T> getType() { return mType; }
@Override public boolean isAbsent() { return true; }
private FallbackField(final Class<T> type, final T value) { mType = type; mValue = value; }
private final Class<T> mType;
private final T mValue;
}
public static class HackedTargetFieldImpl<T> implements HackedTargetField<T> {
@Override public T get() {
if (mField == null) return mFallbackValue;
try {
@SuppressWarnings("unchecked") final T value = (T) mField.get(mInstance);
return value;
} catch (final IllegalAccessException e) { return null; } // Should never happen
}
@Override public void set(final T value) {
if (mField != null) try {
mField.set(mInstance, value);
} catch (final IllegalAccessException ignored) {} // Should never happen
}
@Override @SuppressWarnings("unchecked") public @Nullable Class<T> getType() {
return (Class<T>) mField.getType();
}
@Override public boolean isAbsent() { return mField == null; }
HackedTargetFieldImpl(final Field field, final @Nullable Object instance) {
mField = field;
mInstance = instance;
}
private final Field mField;
private final Object mInstance; // Instance type is already checked
private @Nullable T mFallbackValue;
}
public interface HackedInvokable<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
@CheckResult
<TT1 extends Throwable> HackedInvokable<R, C, TT1, T2, T3> throwing(Class<TT1> type);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable> HackedInvokable<R, C, TT1, TT2, T3> throwing(Class<TT1> type1, Class<TT2> type2);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> HackedInvokable<R, C, TT1, TT2, TT3> throwing(Class<TT1> type1, Class<TT2> type2, Class<TT3> type3);
@Nullable HackedMethod0<R, C, T1, T2, T3> withoutParams();
@Nullable <A1> HackedMethod1<R, C, T1, T2, T3, A1> withParam(Class<A1> type);
@Nullable <A1, A2> HackedMethod2<R, C, T1, T2, T3, A1, A2> withParams(Class<A1> type1, Class<A2> type2);
@Nullable <A1, A2, A3> HackedMethod3<R, C, T1, T2, T3, A1, A2, A3> withParams(Class<A1> type1, Class<A2> type2, Class<A3> type3);
@Nullable <A1, A2, A3, A4> HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4> withParams(Class<A1> type1, Class<A2> type2, Class<A3> type3, Class<A4> type4);
@Nullable <A1, A2, A3, A4, A5> HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5> withParams(Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4, final Class<A5> type5);
@Nullable HackedMethodN<R, C, T1, T2, T3> withParams(Class<?>... types);
}
public interface NonNullHackedInvokable<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends HackedInvokable<R, C, T1,T2,T3> {
@CheckResult <TT1 extends Throwable> NonNullHackedInvokable<R, C, TT1, T2, T3> throwing(Class<TT1> type);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable> NonNullHackedInvokable<R, C, TT1, TT2, T3> throwing(Class<TT1> type1, Class<TT2> type2);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> NonNullHackedInvokable<R, C, TT1, TT2, TT3> throwing(Class<TT1> type1, Class<TT2> type2, Class<TT3> type3);
@NonNull HackedMethod0<R, C, T1, T2, T3> withoutParams();
@NonNull <A1> HackedMethod1<R, C, T1, T2, T3, A1> withParam(Class<A1> type);
@NonNull <A1, A2> HackedMethod2<R, C, T1, T2, T3, A1, A2> withParams(Class<A1> type1, Class<A2> type2);
@NonNull <A1, A2, A3> HackedMethod3<R, C, T1, T2, T3, A1, A2, A3> withParams(Class<A1> type1, Class<A2> type2, Class<A3> type3);
@NonNull <A1, A2, A3, A4> HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4> withParams(Class<A1> type1, Class<A2> type2, Class<A3> type3, Class<A4> type4);
@NonNull <A1, A2, A3, A4, A5> HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5> withParams(Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4, final Class<A5> type5);
@NonNull HackedMethodN<R, C, T1, T2, T3> withParams(Class<?>... types);
}
public interface HackedMethod<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends HackedInvokable<R, C, T1,T2,T3> {
/** Optional */
@CheckResult <RR> HackedMethod<RR, C, T1, T2, T3> returning(Class<RR> type);
/** Fallback to the given value if this field is unavailable at runtime. (Optional) */
@CheckResult NonNullHackedMethod<R, C, T1, T2, T3> fallbackReturning(R return_value);
@CheckResult <TT1 extends Throwable> HackedMethod<R, C, TT1, T2, T3> throwing(Class<TT1> type);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable> HackedMethod<R, C, TT1, TT2, T3> throwing(Class<TT1> type1, Class<TT2> type2);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> HackedMethod<R, C, TT1, TT2, TT3> throwing(Class<TT1> type1, Class<TT2> type2, Class<TT3> type3);
@CheckResult HackedMethod<R, C, Exception, T2, T3> throwing(Class<?>... types);
}
@SuppressWarnings("NullableProblems") // Force to NonNull
public interface NonNullHackedMethod<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends HackedMethod<R, C, T1,T2,T3>, NonNullHackedInvokable<R, C, T1,T2,T3> {
/** Optional */
@CheckResult <RR> HackedMethod<RR, C, T1, T2, T3> returning(Class<RR> type);
@CheckResult <TT1 extends Throwable> NonNullHackedMethod<R, C, TT1, T2, T3> throwing(Class<TT1> type);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable> NonNullHackedMethod<R, C, TT1, TT2, T3> throwing(Class<TT1> type1, Class<TT2> type2);
@CheckResult <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> NonNullHackedMethod<R, C, TT1, TT2, TT3> throwing(Class<TT1> type1, Class<TT2> type2, Class<TT3> type3);
}
public static class CheckedHackedMethod<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
CheckedHackedMethod(final Invokable invokable) { mInvokable = invokable; }
@SuppressWarnings("unchecked") public Class<R> getReturnType() { return (Class<R>) mInvokable.getReturnType(); }
protected HackInvocation<R, C, T1, T2, T3> invoke(final Object... args) { return new HackInvocation<>(mInvokable, args); }
/** Whether this hack is absent, thus will be fallen-back when invoked */
public boolean isAbsent() { return mInvokable instanceof FallbackInvokable; }
private final Invokable mInvokable;
}
public static class HackedMethod0<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod0(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke() { return super.invoke(); }
}
public static class HackedMethod1<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod1(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg) { return super.invoke(arg); }
}
public static class HackedMethod2<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod2(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2) { return super.invoke(arg1, arg2); }
}
public static class HackedMethod3<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod3(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3) { return super.invoke(arg1, arg2, arg3); }
}
public static class HackedMethod4<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod4(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3, final A4 arg4) { return super.invoke(arg1, arg2, arg3, arg4); }
}
public static class HackedMethod5<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, A1, A2, A3, A4, A5> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethod5(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final A1 arg1, final A2 arg2, final A3 arg3, final A4 arg4, final A5 arg5) { return super.invoke(arg1, arg2, arg3, arg4, arg5); }
}
public static class HackedMethodN<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> extends CheckedHackedMethod<R, C, T1,T2,T3> {
HackedMethodN(final Invokable invokable) { super(invokable); }
public @CheckResult HackInvocation<R, C, T1, T2, T3> invoke(final Object... args) { return super.invoke(args); }
}
public static class HackInvocation<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> {
HackInvocation(final Invokable invokable, final Object... args) {
this.invokable = invokable;
this.args = args;
}
public R on(final @NonNull C target) throws T1, T2, T3 { return onTarget(target); }
public R statically() throws T1, T2, T3 { return onTarget(null); }
private R onTarget(final C target) throws T1 { //noinspection TryWithIdenticalCatches
try {
@SuppressWarnings("unchecked") final R result = (R) invokable.invoke(target, args);
return result;
} catch (final IllegalAccessException e) { throw new RuntimeException(e); // Should never happen
} catch (final InstantiationException e) { throw new RuntimeException(e);
} catch (final InvocationTargetException e) {
final Throwable ex = e.getTargetException();
//noinspection unchecked
throw (T1) ex;
}
}
private final Invokable invokable;
private final Object[] args;
}
interface Invokable<C> {
Object invoke(C target, Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException;
Class<?> getReturnType();
}
private static class HackedMethodImpl<R, C, T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> implements NonNullHackedMethod<R, C, T1, T2, T3> {
HackedMethodImpl(final Class<?> clazz, @Nullable final String name, final int modifiers) {
//noinspection unchecked, to be compatible with HackedClass.staticMethod()
mClass = (Class<C>) clazz;
mName = name;
mModifiers = modifiers;
}
@Override public <RR> HackedMethod<RR, C, T1, T2, T3> returning(final Class<RR> type) {
mReturnType = type;
@SuppressWarnings("unchecked") final HackedMethod<RR, C, T1, T2, T3> casted = (HackedMethod<RR, C, T1, T2, T3>) this;
return casted;
}
@Override public NonNullHackedMethod<R, C, T1, T2, T3> fallbackReturning(final R value) {
mFallbackReturnValue = value; mHasFallback = true; return this;
}
@Override public <TT extends Throwable> NonNullHackedMethod<R, C, TT, T2, T3> throwing(final Class<TT> type) {
mThrowTypes = new Class[] { type };
@SuppressWarnings("unchecked") final NonNullHackedMethod<R, C, TT, T2, T3> casted = (NonNullHackedMethod<R, C, TT, T2, T3>) this;
return casted;
}
@Override public <TT1 extends Throwable, TT2 extends Throwable> NonNullHackedMethod<R, C, TT1, TT2, T3>
throwing(final Class<TT1> type1, final Class<TT2> type2) {
mThrowTypes = new Class<?>[] { type1, type2 };
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
@SuppressWarnings("unchecked") final NonNullHackedMethod<R, C, TT1, TT2, T3> cast = (NonNullHackedMethod<R, C, TT1, TT2, T3>) this;
return cast;
}
@Override public <TT1 extends Throwable, TT2 extends Throwable, TT3 extends Throwable> NonNullHackedMethod<R, C, TT1, TT2, TT3>
throwing(final Class<TT1> type1, final Class<TT2> type2, final Class<TT3> type3) {
mThrowTypes = new Class<?>[] { type1, type2, type3 };
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
@SuppressWarnings("unchecked") final NonNullHackedMethod<R, C, TT1, TT2, TT3> cast = (NonNullHackedMethod<R, C, TT1, TT2, TT3>) this;
return cast;
}
@Override public HackedMethod<R, C, Exception, T2, T3> throwing(final Class<?>... types) {
mThrowTypes = types;
Arrays.sort(mThrowTypes, CLASS_COMPARATOR);
@SuppressWarnings("unchecked") final HackedMethod<R, C, Exception, T2, T3> cast = (HackedMethod<R, C, Exception, T2, T3>) this;
return cast;
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public HackedMethod0<R, C, T1, T2, T3> withoutParams() {
final Invokable<C> invokable = findInvokable();
return invokable == null ? null : new HackedMethod0<R, C, T1, T2, T3>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public <A1> HackedMethod1<R, C, T1, T2, T3, A1> withParam(final Class<A1> type) {
final Invokable invokable = findInvokable(type);
return invokable == null ? null : new HackedMethod1<R, C, T1, T2, T3, A1>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public <A1, A2> HackedMethod2<R, C, T1, T2, T3, A1, A2> withParams(final Class<A1> type1, final Class<A2> type2) {
final Invokable invokable = findInvokable(type1, type2);
return invokable == null ? null : new HackedMethod2<R, C, T1, T2, T3, A1, A2>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public <A1, A2, A3> HackedMethod3<R, C, T1, T2, T3, A1, A2, A3> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3) {
final Invokable invokable = findInvokable(type1, type2, type3);
return invokable == null ? null : new HackedMethod3<R, C, T1, T2, T3, A1, A2, A3>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public <A1, A2, A3, A4> HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4) {
final Invokable invokable = findInvokable(type1, type2, type3, type4);
return invokable == null ? null : new HackedMethod4<R, C, T1, T2, T3, A1, A2, A3, A4>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public <A1, A2, A3, A4, A5> HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5> withParams(final Class<A1> type1, final Class<A2> type2, final Class<A3> type3, final Class<A4> type4, final Class<A5> type5) {
final Invokable invokable = findInvokable(type1, type2, type3, type4, type5);
return invokable == null ? null : new HackedMethod5<R, C, T1, T2, T3, A1, A2, A3, A4, A5>(invokable);
}
@NonNull @SuppressWarnings("ConstantConditions")
@Override public HackedMethodN<R, C, T1, T2, T3> withParams(final Class<?>... types) {
final Invokable invokable = findInvokable(types);
return invokable == null ? null : new HackedMethodN<R, C, T1, T2, T3>(invokable);
}
private @Nullable Invokable<C> findInvokable(final Class<?>... param_types) {
if (mClass == ANY_TYPE) // AnyType as a internal indicator for class not found.
return mHasFallback ? new FallbackInvokable<C>(mFallbackReturnValue) : null;
final int modifiers; Invokable<C> invokable; final AccessibleObject accessible; final Class<?>[] ex_types;
try {
if (mName != null) {
final Method candidate = mClass.getDeclaredMethod(mName, param_types); Method method = candidate;
ex_types = candidate.getExceptionTypes();
modifiers = method.getModifiers();
if (Modifier.isStatic(mModifiers) != Modifier.isStatic(candidate.getModifiers())) {
fail(new AssertionException(candidate + (Modifier.isStatic(mModifiers) ? " is not static" : "is static")).setHackedMethod(method));
method = null;
}
if (mReturnType != null && mReturnType != ANY_TYPE && ! candidate.getReturnType().equals(mReturnType)) {
fail(new AssertionException("Return type mismatch: " + candidate));
method = null;
}
if (method != null) {
invokable = new InvokableMethod<>(method);
accessible = method;
} else { invokable = null; accessible = null; }
} else {
final Constructor<C> ctor = mClass.getDeclaredConstructor(param_types);
modifiers = ctor.getModifiers(); invokable = new InvokableConstructor<>(ctor); accessible = ctor;
ex_types = ctor.getExceptionTypes();
}
} catch (final NoSuchMethodException e) {
fail(new AssertionException(e).setHackedClass(mClass).setHackedMethodName(mName).setParamTypes(param_types));
return mHasFallback ? new FallbackInvokable<C>(mFallbackReturnValue) : null;
}
if (mModifiers > 0 && (modifiers & mModifiers) != mModifiers)
fail(new AssertionException(invokable + " does not match modifiers: " + mModifiers).setHackedMethodName(mName));
if (mThrowTypes == null && ex_types.length > 0 || mThrowTypes != null && ex_types.length == 0) {
fail(new AssertionException("Checked exception(s) not match: " + invokable));
if (ex_types.length > 0) invokable = null; // No need to fall-back if expected checked exceptions are absent.
} else if (mThrowTypes != null) {
Arrays.sort(ex_types, CLASS_COMPARATOR);
if (! Arrays.equals(ex_types, mThrowTypes)) { // TODO: Check derived relation of exceptions
fail(new AssertionException("Checked exception(s) not match: " + invokable));
invokable = null;
}
}
if (invokable == null) {
if (! mHasFallback) return null;
return new FallbackInvokable<>(mFallbackReturnValue);
}
if (! accessible.isAccessible()) accessible.setAccessible(true);
return invokable;
}
private final Class<C> mClass;
private final @Nullable String mName; // Null for constructor
private final int mModifiers;
private Class<?> mReturnType;
private Class<?>[] mThrowTypes;
private R mFallbackReturnValue;
private boolean mHasFallback;
private static final Comparator<Class> CLASS_COMPARATOR = new Comparator<Class>() {
@Override public int compare(final Class lhs, final Class rhs) {
return lhs.toString().compareTo(rhs.toString());
}
@Override public boolean equals(final Object object) {
return this == object;
}
};
}
private static class InvokableMethod<C> implements Invokable<C> {
InvokableMethod(final Method method) { this.method = method; }
public Object invoke(final C target, final Object[] args) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
return method.invoke(target, args);
}
@Override public Class<?> getReturnType() { return method.getReturnType(); }
@Override public String toString() { return method.toString(); }
private final Method method;
}
private static class InvokableConstructor<C> implements Invokable<C> {
InvokableConstructor(final Constructor<C> method) { this.constructor = method; }
public Object invoke(final C target, final Object[] args) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return constructor.newInstance(args);
}
@Override public Class<?> getReturnType() {
return constructor.getDeclaringClass();
}
@Override public String toString() { return constructor.toString(); }
private final Constructor<C> constructor;
}
private static class FallbackInvokable<C> implements Invokable<C> {
FallbackInvokable(final @Nullable Object value) { mValue = value; }
@Override public Object invoke(final C target, final Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
return mValue;
}
@Override public Class<?> getReturnType() {
return mValue == null ? Object.class : mValue.getClass();
}
private final @Nullable Object mValue;
}
public static class HackedClass<C> {
public @CheckResult <T> MemberFieldToHack<C> field(final @NonNull String name) {
return new MemberFieldToHack<>(mClass, name, 0);
}
public @CheckResult <T> StaticFieldToHack<C> staticField(final @NonNull String name) {
return new StaticFieldToHack<>(mClass, name, Modifier.STATIC);
}
public @CheckResult NonNullHackedMethod<Void, C, Unchecked, Unchecked, Unchecked> method(final String name) {
return new HackedMethodImpl<>(mClass, name, 0);
}
public @CheckResult NonNullHackedMethod<Void, Void, Unchecked, Unchecked, Unchecked> staticMethod(final String name) {
return new HackedMethodImpl<>(mClass, name, Modifier.STATIC);
}
public @CheckResult NonNullHackedInvokable<C, Void, Unchecked, Unchecked, Unchecked> constructor() {
final HackedMethodImpl<C, Void, Unchecked, Unchecked, Unchecked> constructor = new HackedMethodImpl<>(mClass, null, 0);
constructor.fallbackReturning(null); // Always fallback to null.
return constructor;
}
HackedClass(final Class<C> clazz) { mClass = clazz; }
private final Class<C> mClass;
}
public static <T> HackedClass<T> into(final @NonNull Class<T> clazz) {
return new HackedClass<>(clazz);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <T> HackedClass<T> into(final String class_name) {
try {
return new HackedClass(Class.forName(class_name));
} catch (final ClassNotFoundException e) {
fail(new AssertionException(e));
return new HackedClass(ANY_TYPE); // Use AnyType as a lazy trick to make fallback working and avoid null.
}
}
@SuppressWarnings("unchecked") public static <C> Hack.HackedClass<C> onlyIf(final boolean condition, final Hacking<Hack.HackedClass<C>> hacking) {
if (condition) return hacking.hack();
return (Hack.HackedClass<C>) FALLBACK;
}
public interface Hacking<T> { T hack(); }
private static final Hack.HackedClass<?> FALLBACK = new HackedClass<>(ANY_TYPE);
public static ConditionalHack onlyIf(final boolean condition) {
return condition ? new ConditionalHack() {
@Override public <T> HackedClass<T> into(@NonNull final Class<T> clazz) {
return Hack.into(clazz);
}
@Override public <T> HackedClass<T> into(final String class_name) {
return Hack.into(class_name);
}
} : new ConditionalHack() {
@SuppressWarnings("unchecked") @Override public <T> HackedClass<T> into(@NonNull final Class<T> clazz) {
return (HackedClass<T>) FALLBACK;
}
@SuppressWarnings("unchecked") @Override public <T> HackedClass<T> into(final String class_name) {
return (HackedClass<T>) FALLBACK;
}
};
}
public interface ConditionalHack {
/** WARNING: Never use this method if the target class may not exist when the condition is not met, use {@link #onlyIf(boolean, Hacking)} instead. */
<T> HackedClass<T> into(final @NonNull Class<T> clazz);
<T> HackedClass<T> into(final String class_name);
}
private static void fail(final AssertionException e) {
if (sFailureHandler != null) sFailureHandler.onAssertionFailure(e);
}
/** Specify a handler to deal with assertion failure, and decide whether the failure should be thrown. */
public static AssertionFailureHandler setAssertionFailureHandler(final AssertionFailureHandler handler) {
final AssertionFailureHandler old = sFailureHandler;
sFailureHandler = handler;
return old;
}
private Hack() {}
private static AssertionFailureHandler sFailureHandler;
/** This is a simple demo for the common usage of {@link Hack} */
@SuppressWarnings("unused")
private static class Demo {
@SuppressWarnings({"FieldCanBeLocal", "UnnecessarilyQualifiedStaticUsage"})
static class Hacks {
static {
Hack.setAssertionFailureHandler(new AssertionFailureHandler() { @Override public void onAssertionFailure(final AssertionException failure) {
Log.w("Demo", "Partially incompatible: " + failure.getDebugInfo());
// Report the incompatibility silently.
//...
}});
Demo_ctor = Hack.into(Demo.class).constructor().withParam(int.class);
// Method without fallback (will be null if absent)
Demo_methodThrows = Hack.into(Demo.class).method("methodThrows").returning(Void.class).fallbackReturning(null)
.throwing(InterruptedException.class, IOException.class).withoutParams();
// Method with fallback (will never be null)
Demo_staticMethod = Hack.into(Demo.class).staticMethod("methodWith2Params").returning(boolean.class)
.fallbackReturning(false).withParams(int.class, String.class);
Demo_mField = Hack.into(Demo.class).field("mField").fallbackTo(false);
Demo_sField = Hack.into(Demo.class).staticField("sField").ofType(String.class);
}
static HackedMethod1<Demo, Void, Unchecked, Unchecked, Unchecked, Integer> Demo_ctor;
static Hack.HackedMethod0<Void, Demo, InterruptedException, IOException, Unchecked> Demo_methodThrows;
static Hack.HackedMethod2<Boolean, Void, Unchecked, Unchecked, Unchecked, Integer, String> Demo_staticMethod;
static @Nullable HackedField<Demo, Boolean> Demo_mField; // Optional hack may be null if assertion failed
static @Nullable HackedTargetField<String> Demo_sField;
}
static void demo() {
final Demo demo = Hacks.Demo_ctor.invoke(0).statically();
try {
Hacks.Demo_methodThrows.invoke().on(demo);
} catch (final InterruptedException | IOException e) { // The checked exceptions declared by throwing() in hack definition.
e.printStackTrace();
}
Hacks.Demo_staticMethod.invoke(1, "xx").statically();
}
Demo(final int flags) {}
private void methodThrows() throws InterruptedException, IOException {}
static boolean staticMethod(final int a, final String c) { return false; }
boolean mField;
static String sField;
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="4dp" />
<color android:color="#034abe" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="4dp" />
<color android:color="#034ABE" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="4dp" />
<color android:color="#034abe" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,47 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="320dp"
android:height="180dp"
android:viewportWidth="320"
android:viewportHeight="180">
<group android:scaleX="0.6666667"
android:scaleY="0.6666667"
android:translateX="53.333332"
android:translateY="30">
<group android:scaleX="0.21501707"
android:scaleY="0.21501707"
android:translateX="35.81075"
android:translateY="52.2">
<path
android:pathData="M312.2,93.7c-0.7,-0.5 -1.4,-1.1 -2.1,-1.6l-38.6,60.3c5.8,6.3 9.1,14.6 9.1,23.4c0.1,11.5 -5.5,22.1 -15,28.6c-4.7,3.3 -9.9,5.2 -15.4,6.1c-5.4,0.8 -11,0.7 -16.5,0.7l-45.4,70.8h35.1c6.7,0 13.4,0 20.1,-0.1c9.1,-0.3 17.8,-1.3 26.7,-3.4c15.4,-3.7 29.6,-10.6 41.9,-20.5c24.9,-20 39.3,-50.1 39.3,-82.1C351.5,143.8 337.1,113.7 312.2,93.7z"
android:fillColor="#1C1E20"/>
<path
android:pathData="M108.9,281.8l45.3,-70.6h-36c-5.7,0 -11.2,0.1 -16.7,-0.7c-5.7,-0.8 -10.8,-2.8 -15.4,-6.1c-9.3,-6.5 -15,-17.1 -15,-28.6c0,-11.5 5.7,-22.1 15,-28.6c4.7,-3.3 9.9,-5.2 15.4,-6.1c5.5,-0.8 11.2,-0.7 16.7,-0.7h81.4l45.3,-70.6c-0.5,0 -0.9,0 -1.4,-0.1c-6.7,-0.3 -13.4,-0.1 -20.1,-0.1h-95.2c-6.7,0 -13.4,0 -20.1,0.1c-9.1,0.3 -17.8,1.3 -26.7,3.4c-15.4,3.7 -29.6,10.6 -41.9,20.5C14.4,113.7 0,143.8 0,175.8c0,32 14.4,62.1 39.3,82.1c12.3,9.9 26.6,16.8 42,20.5c8.8,2.1 17.7,3.1 26.7,3.4C108.4,281.8 108.6,281.8 108.9,281.8z"
android:fillColor="#1C1E20"/>
<path
android:pathData="M220.523,9.533l56.401,36.167l-190.01,296.314l-56.401,-36.167z"
android:fillColor="#1C1E20"/>
</group>
<group android:scaleX="0.27198938"
android:scaleY="0.27198938"
android:translateX="128"
android:translateY="67.20389">
<group android:translateY="133.66406">
<path android:pathData="M43.03125,1Q6,1,6,-51.671875Q6,-104,43.171875,-104Q61,-104,70.5,-90.359375Q80,-76.71875,80,-51.53125Q80,-26.34375,70.390625,-12.671875Q60.78125,1,43.03125,1ZM43.03125,-10.53125Q55,-10.53125,60.5625,-20.609375Q66.140625,-30.703125,66.140625,-51.5625Q66.140625,-72.640625,60.5,-82.546875Q54.859375,-92.46875,43.171875,-92.46875Q19.859375,-92.46875,19.859375,-51.5625Q19.859375,-10.53125,43.03125,-10.53125Z"
android:fillColor="#000000"/>
<path android:pathData="M160,-74.859375L155.6875,-63.75Q145.25,-67.8125,137.03125,-67.8125Q111.21875,-67.8125,111.21875,-38.75Q111.21875,-10.1875,136.51562,-10.1875Q147.51562,-10.1875,159,-14.546875L159,-3.296875Q149.60938,1,135.9375,1Q117.90625,1,107.953125,-9.21875Q98,-19.453125,98,-38.578125Q98,-58.328125,108.109375,-68.65625Q118.21875,-79,136.5625,-79Q148.98438,-79,160,-74.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M236.67188,0L234.76562,-10.75L234.0625,-10.75Q226.67188,1,210.53125,1Q183,1,183,-26.984375L183,-77L195.79688,-77L195.79688,-27.734375Q195.79688,-9.625,212.42188,-9.625Q223.78125,-9.625,228.98438,-16.0625Q234.20312,-22.515625,234.20312,-37.28125L234.20312,-77L247,-77L247,0L236.67188,0Z"
android:fillColor="#000000"/>
<path android:pathData="M295,-98.9375L276.15625,-100.421875L276.15625,-109L307.79688,-109L307.79688,-10.0625L332.54688,-8.65625L332.54688,0L270.8125,0L270.8125,-8.65625L295,-10.0625L295,-98.9375Z"
android:fillColor="#000000"/>
<path android:pathData="M421,-36.375L366.21875,-36.375Q366.78125,-10.1875,390.57812,-10.1875Q404.39062,-10.1875,416.78125,-15.53125L416.78125,-4.28125Q405.0625,1,391.07812,1Q373.76562,1,363.375,-9.5Q353,-20.015625,353,-38.4375Q353,-57.140625,362.5625,-68.0625Q372.14062,-79,388.17188,-79Q403.14062,-79,412.0625,-69.546875Q421,-60.09375,421,-44.34375L421,-36.375ZM366.5,-47L407.78125,-47Q407.78125,-68.375,388.1875,-68.375Q368.375,-68.375,366.5,-47Z"
android:fillColor="#000000"/>
<path android:pathData="M444,-3.859375L444,-15.53125Q457.60938,-9.625,470.09375,-9.625Q489.48438,-9.625,489.48438,-20.859375Q489.48438,-24.859375,486.01562,-27.8125Q482.54688,-30.765625,470.03125,-35.265625Q453.5625,-41.390625,449.28125,-46.484375Q445,-51.578125,445,-58.546875Q445,-68.109375,452.90625,-73.546875Q460.82812,-79,474.8125,-79Q488.85938,-79,501,-73.796875L496.75,-63.328125Q483.89062,-68.375,474.20312,-68.375Q457.51562,-68.375,457.51562,-59.03125Q457.51562,-54.75,461.0625,-52.171875Q464.60938,-49.609375,477.40625,-45.046875Q492.23438,-39.625,497.10938,-34.390625Q502,-29.15625,502,-21.5625Q502,-11.015625,493.75,-5Q485.51562,1,470.42188,1Q452.95312,1,444,-3.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M530,-3.859375L530,-15.53125Q543.6094,-9.625,556.09375,-9.625Q575.4844,-9.625,575.4844,-20.859375Q575.4844,-24.859375,572.0156,-27.8125Q568.5469,-30.765625,556.03125,-35.265625Q539.5625,-41.390625,535.28125,-46.484375Q531,-51.578125,531,-58.546875Q531,-68.109375,538.90625,-73.546875Q546.8281,-79,560.8125,-79Q574.8594,-79,587,-73.796875L582.75,-63.328125Q569.8906,-68.375,560.2031,-68.375Q543.5156,-68.375,543.5156,-59.03125Q543.5156,-54.75,547.0625,-52.171875Q550.6094,-49.609375,563.40625,-45.046875Q578.2344,-39.625,583.1094,-34.390625Q588,-29.15625,588,-21.5625Q588,-11.015625,579.75,-5Q571.5156,1,556.4219,1Q538.9531,1,530,-3.859375Z"
android:fillColor="#000000"/>
</group>
</group>
</group>
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.12576792"
android:scaleY="0.12576792"
android:translateX="31.896288"
android:translateY="31.89">
<path
android:pathData="M312.2,93.7c-0.7,-0.5 -1.4,-1.1 -2.1,-1.6l-38.6,60.3c5.8,6.3 9.1,14.6 9.1,23.4c0.1,11.5 -5.5,22.1 -15,28.6c-4.7,3.3 -9.9,5.2 -15.4,6.1c-5.4,0.8 -11,0.7 -16.5,0.7l-45.4,70.8h35.1c6.7,0 13.4,0 20.1,-0.1c9.1,-0.3 17.8,-1.3 26.7,-3.4c15.4,-3.7 29.6,-10.6 41.9,-20.5c24.9,-20 39.3,-50.1 39.3,-82.1C351.5,143.8 337.1,113.7 312.2,93.7z"
android:fillColor="#1C1E20"/>
<path
android:pathData="M108.9,281.8l45.3,-70.6h-36c-5.7,0 -11.2,0.1 -16.7,-0.7c-5.7,-0.8 -10.8,-2.8 -15.4,-6.1c-9.3,-6.5 -15,-17.1 -15,-28.6c0,-11.5 5.7,-22.1 15,-28.6c4.7,-3.3 9.9,-5.2 15.4,-6.1c5.5,-0.8 11.2,-0.7 16.7,-0.7h81.4l45.3,-70.6c-0.5,0 -0.9,0 -1.4,-0.1c-6.7,-0.3 -13.4,-0.1 -20.1,-0.1h-95.2c-6.7,0 -13.4,0 -20.1,0.1c-9.1,0.3 -17.8,1.3 -26.7,3.4c-15.4,3.7 -29.6,10.6 -41.9,20.5C14.4,113.7 0,143.8 0,175.8c0,32 14.4,62.1 39.3,82.1c12.3,9.9 26.6,16.8 42,20.5c8.8,2.1 17.7,3.1 26.7,3.4C108.4,281.8 108.6,281.8 108.9,281.8z"
android:fillColor="#1C1E20"/>
<path
android:pathData="M220.523,9.533l56.401,36.167l-190.01,296.314l-56.401,-36.167z"
android:fillColor="#1C1E20"/>
</group>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topLeftRadius="15dp" android:topRightRadius="15dp" />
</shape>
</item>
</selector>

View File

@ -4,65 +4,141 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".MainActivity">
<View
android:id="@+id/view2"
android:layout_width="99999dp"
android:layout_height="99999dp"
android:background="@drawable/windowbg"
android:backgroundTint="#D8202020"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/viewOtaBtn"
android:layout_width="217dp"
android:layout_height="87dp"
style="@style/OculusButtonStyle"
android:layout_width="450dp"
android:layout_height="50dp"
android:layout_marginTop="12dp"
android:background="@drawable/buttonbg"
android:backgroundTint="#024abf"
android:text="@string/disable_ota"
app:icon="@android:drawable/ic_popup_sync"
app:iconPadding="-25dp"
app:iconSize="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.494"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/viewAccountsBtn" />
<Button
android:id="@+id/viewTelemetryBtn"
android:layout_width="217dp"
android:layout_height="87dp"
style="@style/OculusButtonStyle"
android:layout_width="450dp"
android:layout_height="50dp"
android:layout_marginTop="12dp"
android:background="@drawable/buttonbg"
android:text="@string/telemetry"
app:icon="?android:attr/actionModeFindDrawable"
app:iconPadding="-27dp"
app:iconSize="26dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/viewOtaBtn" />
<TextView
android:id="@+id/isEnabledText"
android:id="@+id/Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginBottom="52dp"
android:text="@string/app_name"
android:textSize="20sp"
android:textColor="@color/white"
android:textSize="34sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/viewAdminsBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/isEnabledText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:textColor="@color/white"
android:textSize="14sp"
android:textStyle="italic"
app:layout_constraintBottom_toTopOf="@+id/viewAdminsBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/isEnabledText2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/app_subtitle"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="normal"
app:layout_constraintBottom_toTopOf="@+id/viewAdminsBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/viewAdminsBtn"
android:layout_width="217dp"
android:layout_height="87dp"
style="@style/OculusButtonStyle"
android:layout_width="450dp"
android:layout_height="50dp"
android:layout_marginBottom="12dp"
android:background="@drawable/buttonbg"
android:backgroundTint="@color/ic_banner_background"
android:backgroundTintMode="multiply"
android:text="@string/disable_companion"
android:typeface="normal"
app:icon="?android:attr/alertDialogIcon"
app:iconPadding="-25dp"
app:iconSize="24dp"
app:layout_constraintBottom_toTopOf="@+id/viewAccountsBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.494"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/viewAccountsBtn"
android:layout_width="217dp"
android:layout_height="87dp"
android:layout_marginBottom="12dp"
style="@style/OculusButtonStyle"
android:layout_width="450dp"
android:layout_height="50dp"
android:layout_marginBottom="8dp"
android:background="@drawable/buttonbg"
android:text="@string/remove_accounts"
app:icon="?attr/actionModeShareDrawable"
app:iconPadding="-25dp"
app:iconSize="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.494"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.362" />
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/viewPermissionsBtn"
style="@style/OculusButtonStyle"
android:layout_width="450dp"
android:layout_height="80dp"
android:layout_marginTop="12dp"
android:background="@drawable/buttonbg"
android:text="@string/audio"
app:icon="@android:drawable/ic_lock_silent_mode_off"
app:iconPadding="-27dp"
app:iconSize="29dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/viewTelemetryBtn" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@drawable/ic_banner_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?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" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?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" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Oculess</string>
<string name="app_subtitle">Modifikationstool für Oculus Quest-Geräte</string>
<string name="is_disabled">Oculus Begleit-Server ist deaktiviert!</string>
<string name="is_enabled">Oculus Begleit-Server ist aktiviert!</string>
<string name="disable_companion">BEGLEITER DEAKTIVIEREN</string>
<string name="enable_companion">BEGLEITER AKTIVIEREN</string>
<string name="remove_accounts">ACCOUNTS ENTFERNEN</string>
<string name="disable_ota">UPDATES DEAKTIVIEREN</string>
<string name="enable_ota">UPDATES AKTIVIEREN</string>
<string name="telemetry">TELEMETRIE</string>
<string name="disable_companion">Begleiter Aktivieren</string>
<string name="enable_companion">Accounts Entfernen</string>
<string name="remove_accounts">Updates Deaktivieren</string>
<string name="disable_ota">Updates Deaktivieren</string>
<string name="enable_ota">Updates Aktivieren</string>
<string name="telemetry">Aktivieren/Deaktivieren Telemetrie</string>
<string name="title">Wichtige Information</string>
<string name="message0">Bitte starten Sie nach dem ersten Mal neu!\nWiederholen Sie den Schritt nach jedem Neustart!</string>
@ -21,4 +22,5 @@
<string name="cancel">Abbrechen</string>
<string name="disable">Telemetrie Deaktivieren</string>
<string name="enable">Telemetrie Aktivieren</string>
<string name="audio">Hintergrundaudio für alle Apps Aktivieren\n(bleibt nach Neustart nicht)</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="OculusButtonStyle" parent="@style/Widget.AppCompat.Button.Borderless">
<item name="android:textAllCaps">false</item>
<item name="android:fontFamily">"sans-serif-light"</item>
<item name="android:textSize">16sp</item>
</style>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -1,18 +1,19 @@
<resources>
<string name="app_name">Oculess</string>
<string name="app_subtitle">Toolbox for Oculus Quest devices</string>
<string name="is_disabled">Oculus Companion Server is disabled!</string>
<string name="is_enabled">Oculus Companion Server is enabled!</string>
<string name="disable_companion">DISABLE COMPANION</string>
<string name="enable_companion">ENABLE COMPANION</string>
<string name="remove_accounts">REMOVE ACCOUNTS</string>
<string name="disable_ota">DISABLE UPDATES</string>
<string name="enable_ota">ENABLE UPDATES</string>
<string name="telemetry">TELEMETRY</string>
<string name="disable_companion">Disable Companion Server</string>
<string name="enable_companion">Enable Companion Server</string>
<string name="remove_accounts">Remove Accounts</string>
<string name="disable_ota">Disable Updates</string>
<string name="enable_ota">Enable Updates</string>
<string name="telemetry">Enable/Disable Telemetry</string>
<string name="title">Important Info</string>
<string name="message0">Please restart after the first time!\nRepeat this step after every restart!</string>
<string name="message0">NOTE: Set light theme, or else text will be white on white\nPlease restart after the first time!\nRepeat this step after every restart!</string>
<string name="message1">Please remove *all* the accounts on your Device!\nRepeat this step after every restart!</string>
<string name="message2">Device Owner has not been set!</string>
<string name="message3">What would you like to do?</string>
@ -20,4 +21,6 @@
<string name="cancel">Cancel</string>
<string name="disable">Disable Telemetry</string>
<string name="enable">Enable Telemetry</string>
<string name="audio">Enable Background Audio for Installed Apps\n(Must reapply after reboot)</string>
</resources>

View File

@ -2,7 +2,7 @@
<!-- Base application theme. -->
<style name="Theme.Oculess" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimary">#024abf</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->