Skip to content

Instantly share code, notes, and snippets.

@RikkaW
Last active March 26, 2024 14:45
Show Gist options
  • Save RikkaW/be3fe4178903702c54ec73b2fc1187fe to your computer and use it in GitHub Desktop.
Save RikkaW/be3fe4178903702c54ec73b2fc1187fe to your computer and use it in GitHub Desktop.
show window in app_process (impossible to work from Android 14 QPR 3 or Android 15, as addView requires the calling process an Android app process)
token = new Binder();
try {
Context _context = android.app.ActivityThread.systemMain().getSystemContext();
final Context context = new ContextWrapper(_context) {
@Override
public Object getSystemService(String name) {
if (Context.WINDOW_SERVICE.equals(name)) {
WindowManager wm = (WindowManager) super.getSystemService(name);
if (wm != null) {
((android.view.WindowManagerImpl) wm).setDefaultToken(token);
}
return wm;
}
return super.getSystemService(name);
}
};
context.setTheme(android.R.style.Theme_Material_Light);
CharSequence label = context.getPackageManager().getApplicationLabel(context.getPackageManager().getApplicationInfo("com.android.settings", 0));
LogUtils.i("label " + label);
android.widget.Toast.makeText(context, "test", Toast.LENGTH_LONG).show();
LogUtils.i("toast");
android.view.WindowManager wm = context.getSystemService(WindowManager.class);
LogUtils.i("class: " + wm.getClass().getName());
//((android.view.WindowManagerImpl) wm).setDefaultToken(token);
LinearLayout root = new LinearLayout(context);
root.setBackground(new ColorDrawable(0xffffffff));
TextView textView = new TextView(context);
textView.setText("test");
root.addView(textView);
Button button = new Button(context);
button.setSoundEffectsEnabled(false);
button.setText("test");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "?!", Toast.LENGTH_LONG).show();
}
});
root.addView(button);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER;
params.x = 0;
params.y = 0;
wm.addView(root, params);
} catch (Throwable tr) {
LogUtils.e(tr.getMessage(), tr);
}
@huynhtanloc2612
Copy link

huynhtanloc2612 commented Jul 28, 2023

Hi @twaik @dyno314 @RikkaW
I tried to compile the code but got below error. It seem that the hiden class can not be found. I already tried android-34 SDK. Please give me your advices. Thanks.

error: unreported exception ClassNotFoundException; must be caught or declared to be thrown
         Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

@huynhtanloc2612
Copy link

I could compile my below code with modified android.jar file which includes hidden classes. My purpose is to show Toast first.

        Binder token = new Binder();
        Looper.prepareMainLooper();
        try {
            Context _context = android.app.ActivityThread.systemMain().getSystemContext();
            final Context context = new ContextWrapper(_context) {
                @Override
                public Object getSystemService(String name) {
                    if (Context.WINDOW_SERVICE.equals(name)) {
                        WindowManager wm = (WindowManager) super.getSystemService(name);
                        if (wm != null) {
                            ((android.view.WindowManagerImpl) wm).setDefaultToken(token);
                        }
                        return wm;
                    }
                    return super.getSystemService(name);
                }
            };
            context.setTheme(android.R.style.Theme_Material_Light);
            android.widget.Toast.makeText(context, "test", Toast.LENGTH_LONG).show();
        } catch (Throwable tr) {
            System.out.println(tr); 
        }

But got below error (tested on Android 7 and 12) when created context instance

java.lang.NoClassDefFoundError: Failed resolution of: LWindow$1;

Any advice for me?
Thanks

@twaik
Copy link

twaik commented Jul 28, 2023

You can make some compileOnly subproject that contains only stubs for hidden methods.
Check my shell-loader.

@huynhtanloc2612
Copy link

You can make some compileOnly subproject that contains only stubs for hidden methods. Check my shell-loader.

Thanks @twaik , I will try this.
Did you make Toast via app_process successfully? And did you face my above error?
java.lang.NoClassDefFoundError: Failed resolution of: LWindow$1;

@twaik
Copy link

twaik commented Jul 28, 2023

I did not make toasts. I am starting app_process not in ADB so it is not possible. But with compileOnly stub dependency you will avoid using reflection or modifying android.jar.
I need to see the whole exception text, not only its description.

@huynhtanloc2612
Copy link

I did not make toasts. I am starting app_process not in ADB so it is not possible. But with compileOnly stub dependency you will avoid using reflection or modifying android.jar. I need to see the whole exception text, not only its description.

Can you tell me how to show whole exception text?
I tried System.out.println(tr.getMessage()) and only got above message.

@twaik
Copy link

twaik commented Jul 28, 2023

tr.printStackTrace(System.err);...

@huynhtanloc2612
Copy link

tr.printStackTrace(System.err);...

Thanks @twaik
It showed below

java.lang.NoClassDefFoundError: Failed resolution of: LWindow$1;
	at Window.main(Window.java:33)
	at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
	at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:378)
Caused by: java.lang.ClassNotFoundException: Window$1
	... 3 more

@huynhtanloc2612
Copy link

Line 33
final Context context = new ContextWrapper(_context) {

@twaik
Copy link

twaik commented Jul 28, 2023

It looks like your class is called Window and it has public static void main(), isn't it?

@huynhtanloc2612
Copy link

It looks like your class is called Window and it has public static void main(), isn't it?

Yes
My class is Window and it has public static void main(String... args)

@twaik
Copy link

twaik commented Jul 28, 2023

I am not sure why but it failed to resolve your custom ContextWrapper. Try to make it separate (named) subclass. No need to override constructor, only getSystemService method.

@huynhtanloc2612
Copy link

I am not sure why but it failed to resolve your custom ContextWrapper. Try to make it separate (named) subclass. No need to override constructor, only getSystemService method.

I don't quite understand your suggestion.
Please correct me if I am misunderstanding it.
You think I should make new subclass called CustomContext for example which extends Context class, right? And can you tell me how to implement the getSystemService method in this new class?

@twaik
Copy link

twaik commented Jul 28, 2023

Not Context. ContextWrapper...

@huynhtanloc2612
Copy link

Not Context. ContextWrapper...

Got it and can you tell me how to implement the getSystemService method in this new class?

@twaik
Copy link

twaik commented Jul 28, 2023

            final Context context = new ContextWrapper(_context) {
                @Override
                public Object getSystemService(String name) {
                    if (Context.WINDOW_SERVICE.equals(name)) {
                        WindowManager wm = (WindowManager) super.getSystemService(name);
                        if (wm != null) {
                            ((android.view.WindowManagerImpl) wm).setDefaultToken(token);
                        }
                        return wm;
                    }
                    return super.getSystemService(name);
                }
            };

This code creates new instance of anonymous class which extends ContextWrapper. My suggestion is to create new class (a named one, not anonymous) and move there the code.
It is pretty much basic Java thing. If you do not know such a thing you should practice a bit and after that go back to this project.

@huynhtanloc2612
Copy link

            final Context context = new ContextWrapper(_context) {
                @Override
                public Object getSystemService(String name) {
                    if (Context.WINDOW_SERVICE.equals(name)) {
                        WindowManager wm = (WindowManager) super.getSystemService(name);
                        if (wm != null) {
                            ((android.view.WindowManagerImpl) wm).setDefaultToken(token);
                        }
                        return wm;
                    }
                    return super.getSystemService(name);
                }
            };

This code creates new instance of anonymous class which extends ContextWrapper. My suggestion is to create new class (a named one, not anonymous) and move there the code. It is pretty much basic Java thing. If you do not know such a thing you should practice a bit and after that go back to this project.

Thanks @twaik for your clarification and I got it.

@huynhtanloc2612
Copy link

Hi @twaik
You was correct.
After I made subclass with a separated file MyContext.class, above error disappeared.

However, after code run, it has new below error.
Do you have any advice?

java.lang.SecurityException: Given calling package android does not match caller's uid 2000 at android.os.Parcel.createExceptionOrNull(Parcel.java:2437) at android.os.Parcel.createException(Parcel.java:2421) at android.os.Parcel.readException(Parcel.java:2404) at android.os.Parcel.readException(Parcel.java:2346) at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:6847) at android.app.ActivityThread.acquireProvider(ActivityThread.java:7767) at android.app.ContextImpl$ApplicationContentResolver.acquireProvider(ContextImpl.java:3508) at android.content.ContentResolver.acquireProvider(ContentResolver.java:2517) at android.provider.Settings$ContentProviderHolder.getProvider(Settings.java:2901) at android.provider.Settings$NameValueCache.getStringForUser(Settings.java:3139) at android.provider.Settings$System.getStringForUser(Settings.java:3857) at android.provider.Settings$System.getIntForUser(Settings.java:3964) at android.provider.Settings$System.getInt(Settings.java:3958) at android.widget.Toast.checkGameHomeAllowList(Toast.java:1012) at android.widget.Toast.show(Toast.java:303) at HelloWorld.main(HelloWorld.java:74) at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:378) Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.am.ContentProviderHelper.getContentProvider(ContentProviderHelper.java:171) at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:7628) at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2881) at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3036) at android.os.Binder.execTransactInternal(Binder.java:1215)

@twaik
Copy link

twaik commented Jul 30, 2023

No

@RikkaW
Copy link
Author

RikkaW commented Jul 30, 2023

Processes started by app_process CANNOT directly use content provider (which is behind Settings) like normal Android apps, as well as registering broadcast receiver.

@twaik
Copy link

twaik commented Jul 30, 2023

His problem appears on android.widget.Toast.show

@RikkaW
Copy link
Author

RikkaW commented Jul 30, 2023

The root problem is processes started by app_process do not have a valid ProcessRecord in AMS, so many APIs available to normal Android apps cannot be used. Many OEMs add random codes that use "unavailable APIs" in many framework classes for their custom features, e.g., View class, XxxManager class, and Toast in his case (checkGameHomeAllowList does not exist in AOSP).
My opinion is, avoid using these "high level" classes like Context, you never know what the OEMs will add to them. Using raw aidl APIs, such as IPackageManager, is much safer. If it is unavoidable, such as displaying a Window, try to use APIs that involves less things. In the worst case, use decompilation to find way to bypasses or "fix" the use of "unavailable APIs".

@huynhtanloc2612
Copy link

Thanks @RikkaW and @twaik for your comments and suggestion.

Using raw aidl APIs, such as IPackageManager, is much safer

I wonder how to use aidl API and how to know which aidl API is used for Toast? It looks hard to know their correlation.

try to use APIs that involves less things

Can you help give me example for "the API that involves less things"?
Thanks.

@RikkaW
Copy link
Author

RikkaW commented Jul 31, 2023

Toast is a window internally; you can show a window which looks like Toast instead. Windows type can be used by app_process is so limited, most of the window types, including TYPE_TOAST, do not work. So, it's not worth trying anymore.
On Android 12+, toast is shown through SystemUI rather than the app itself. The API is INotificationManager#enqueueTextToast, I haven't tried if app_process can use.

Btw, if you want to play app_process magic (and other magic), you MUST learn how things work internally.

@huynhtanloc2612
Copy link

huynhtanloc2612 commented Jul 31, 2023

Thanks @RikkaW for your information and advices.
Such knowledge is completely new to me and it is quite interesting. I sometime play with Android API but since I knew about the app_process magic from scrcpy tool, I was very impresive. I want to learn more about it and find out what else it can do... but looks there is very few online information for beginers. By following and trying some projects like this, the answers of @twaik and yours help me learn a lot new knowledge about it. Thank you.
If posible, I hope you could point me to where to learn and play with aidl API for beginers.

@twaik
Copy link

twaik commented Mar 26, 2024

@RikkaW I have some problems with creating some overlay from process started with app_process in application sandbox of other app.
I know "window" service is inaccessible from processes started this way, but I am sending IBinder of that service from Activity via AIDL and injecting it to ServiceManager's sCache manually, so WindowManagerGlobal can access it.
I am trying to create overlay with this code:

    public void window(IBinder windowManager) {
        try {
            Field sCache = Class.forName("android.os.ServiceManager").getDeclaredField("sCache");
            sCache.setAccessible(true);
            Method m = sCache.getType().getDeclaredMethod("put", Object.class, Object.class);
            m.invoke(sCache.get(null), "window", windowManager);

            handler.post(() -> {
                Context context = ctx;
                WindowManager wm = context.getSystemService(WindowManager.class);
                Binder token = new Binder();
                context = new ContextWrapper(context) {
                    @Override
                    public Object getSystemService(String name) {
                        if (Context.WINDOW_SERVICE.equals(name)) {
                            WindowManager wm = (WindowManager) super.getSystemService(name);
                            if (wm != null)
                                ((android.view.WindowManagerImpl) wm).setDefaultToken(token);
                            return wm;
                        }
                        return super.getSystemService(name);
                    }
                };
                Button button = new Button(context);
                button.setSoundEffectsEnabled(false);
                button.setText("test");
                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                        WindowManager.LayoutParams.WRAP_CONTENT,
                        WindowManager.LayoutParams.WRAP_CONTENT,
                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                        PixelFormat.TRANSLUCENT);

                params.gravity = Gravity.CENTER;
                params.x = 0;
                params.y = 0;
                wm.addView(button, params);
            });
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

But I am getting only this unclear exception

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@e51bfa1 -- permission denied for window type 2038
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:1092)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109)
        at com.termux.x11.CmdEntryPoint.lambda$windowManager$3(CmdEntryPoint.java:269)
        at com.termux.x11.CmdEntryPoint.$r8$lambda$MIV7qHiVNsG1buhNHJnI7A8pV3A(CmdEntryPoint.java:0)
        at com.termux.x11.CmdEntryPoint$$ExternalSyntheticLambda1.run(R8$$SyntheticClass:0)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at com.termux.x11.CmdEntryPoint.main(CmdEntryPoint.java:62)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.termux.x11.Loader.main(Loader.java:26)
        at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
        at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:399)

Both activity I am sending IBinder from and application where I want to start this snippet have "Draw over apps" permission but it does not help.
I tried to make a proxy for IBinder to make my snippet inherit Uid of another application (which owns Activity) with adding this code to ContextWrapper


                    public UserHandle getUser() {
                        try {
                            return (UserHandle) UserHandle.class.getDeclaredMethod("of", int.class).invoke(null, Binder.getCallingUid() / 100000);
                        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    public int getUserId() {
                        return Binder.getCallingUid() / 100000;
                    }

and wrapping "window"'s IBinder with

                    Binder proxy = new Binder() {
                        IBinder target = (IBinder) Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class).invoke(null, "window");
                        @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                            return target.transact(code, data, reply, flags);
                        }
                    };

But it does not seem to work, I am getting the same exception.

Seems like I found a reason in system-wide logcat.

03-26 13:39:36.231   536  2972 E AppOps  : noteOperation
03-26 13:39:36.231   536  2972 E AppOps  : java.lang.SecurityException: Specified package android under uid 10162 but it is really 1000
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.appop.AppOpsService.verifyAndGetBypass(AppOpsService.java:3874)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.appop.AppOpsService.noteOperationUnchecked(AppOpsService.java:3030)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.appop.AppOpsService.noteOperationImpl(AppOpsService.java:3018)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.appop.AppOpsService.noteOperation(AppOpsService.java:3001)
03-26 13:39:36.231   536  2972 E AppOps  :      at android.app.AppOpsManager.noteOpNoThrow(AppOpsManager.java:7382)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.policy.PhoneWindowManager.checkAddPermission(PhoneWindowManager.java:2150)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1383)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.wm.Session.addToDisplayAsUser(Session.java:176)
03-26 13:39:36.231   536  2972 E AppOps  :      at android.view.IWindowSession$Stub.onTransact(IWindowSession.java:679)
03-26 13:39:36.231   536  2972 E AppOps  :      at com.android.server.wm.Session.onTransact(Session.java:139)
03-26 13:39:36.231   536  2972 E AppOps  :      at android.os.Binder.execTransactInternal(Binder.java:1154)
03-26 13:39:36.231   536  2972 E AppOps  :      at android.os.Binder.execTransact(Binder.java:1123)

@twaik
Copy link

twaik commented Mar 26, 2024

Ok, I figured it out.


    public void windowManager(IBinder binder) {
        try {
            Field sCache = Class.forName("android.os.ServiceManager").getDeclaredField("sCache");
            sCache.setAccessible(true);
            Method m = sCache.getType().getDeclaredMethod("put", Object.class, Object.class);
            m.invoke(sCache.get(null), "window", binder);

            handler.post(() -> {
                Context context = android.app.ActivityThread.systemMain().getSystemContext();
                WindowManager wm = context.getSystemService(WindowManager.class);
                Binder token = new Binder();
                context = new ContextWrapper(context) {
                    @Override
                    public Object getSystemService(String name) {
                        if (Context.WINDOW_SERVICE.equals(name)) {
                            WindowManager wm = (WindowManager) super.getSystemService(name);
                            if (wm != null)
                                ((android.view.WindowManagerImpl) wm).setDefaultToken(token);
                            return wm;
                        }
                        return super.getSystemService(name);
                    }

                    @Override
                    public Looper getMainLooper() {
                        return Looper.myLooper();
                    }
                };
                View view = new View(context) {
                    final Paint paint = new Paint();
                    @Override
                    protected void onDraw(@NonNull Canvas canvas) {
                        super.onDraw(canvas);
                        paint.setStyle(Paint.Style.FILL);
                        paint.setColor(Color.RED);
                        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
                    }
                };
                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                        100,  100, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                        PixelFormat.OPAQUE);

                params.gravity = Gravity.CENTER;
                params.x = 0;
                params.y = 0;
                params.packageName = "com.termux";
                wm.addView(view, params);
            });
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

And it works on Android 11.
Thanks @RikkaW.

@twaik
Copy link

twaik commented Mar 26, 2024

And unfortunately I am getting this on Android 12.

java.lang.NullPointerException
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2431)
        at android.os.Parcel.createException(Parcel.java:2409)
        at android.os.Parcel.readException(Parcel.java:2392)
        at android.os.Parcel.readException(Parcel.java:2334)
        at com.android.internal.app.IAppOpsService$Stub$Proxy.reportRuntimeAppOpAccessMessageAndGetConfig(IAppOpsService.java:1666)
        at android.app.AppOpsManager$1.reportStackTraceIfNeeded(AppOpsManager.java:290)
        at android.app.AppOpsManager$1.onNoted(AppOpsManager.java:265)
        at android.app.AppOpsManager.readAndLogNotedAppops(AppOpsManager.java:9359)
        at android.os.Parcel.readExceptionCode(Parcel.java:2356)
        at android.os.Parcel.readException(Parcel.java:2331)
        at android.view.IWindowSession$Stub$Proxy.addToDisplayAsUser(IWindowSession.java:1374)
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:1119)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:399)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:133)
        at com.termux.x11.CmdEntryPoint.lambda$windowManager$3(CmdEntryPoint.java:286)
        at com.termux.x11.CmdEntryPoint.$r8$lambda$MIV7qHiVNsG1buhNHJnI7A8pV3A(CmdEntryPoint.java:0)
        at com.termux.x11.CmdEntryPoint$$ExternalSyntheticLambda1.run(R8$$SyntheticClass:0)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at com.termux.x11.CmdEntryPoint.main(CmdEntryPoint.java:71)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.termux.x11.Loader.main(Loader.java:26)
        at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
        at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:355)
Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.internal.util.AnnotationValidations.validate(AnnotationValidations.java:129)
        at android.app.SyncNotedAppOp.<init>(SyncNotedAppOp.java:227)
        at android.app.SyncNotedAppOp$1.createFromParcel(SyncNotedAppOp.java:243)
        at android.app.SyncNotedAppOp$1.createFromParcel(SyncNotedAppOp.java:235)
        at com.android.internal.app.IAppOpsService$Stub.onTransact(IAppOpsService.java:728)

Any suggestions?

@RikkaW
Copy link
Author

RikkaW commented Mar 26, 2024

Change opPackageName? I don't remember:(

Btw, this will never work starting from Android 14 QPR 3 or Android 15, as addView now checks if the calling process is a valid Android app process.

@twaik
Copy link

twaik commented Mar 26, 2024

Maybe there is some workaround for this? The most important thing I need is to draw something as an overlay from background process.
I am not writing viruses. Simply some oems put code to limit GPU performance of background apps.
Maybe SurfaceControl.Transactions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment