The first time I came into contact with Android decompilation was when I was in junior high school. At that time, I would decompilate and modify some things, but there was no support of native development technology

Let’s start by sharing a few of my own experiences with decompiling and embedding layouts

  • Do not touch resources.arsc(decomcompile/backcompile is prone to failure, easily mess with other files’ addresses)
  • Do not embed statements like findViewById
  • Use a tag binding layout
  • Use custom views whenever possible
  • Replace all findViewByIds with findViewByTag
  • Try not to populate an entire XML file (populate can change a lot of XML addresses)
  • Any R.ID /R.layout in Java that eventually compiles to class/smali becomes a hexadecimal 0x** address

Knowledge of the following is required:

  • Android native development foundation
  • Android native custom View
  • Content observer
  • Update the View with Handler
  • Simple thread correlation

The software used

  • MT Manager (using its text modification feature)
  • Apktool decompiler (here I use my own MToolkit, as well as foreign Apktool of the same name)
  • AIDE(Android Studio, etc.)

Before we embed any View, we need to make sure that the View is displayed correctly, that is, we need to run a demo. This post we put into the status bar of the phone

The AIDE part

1. Create an empty project

2. Click on the upper right corner to run this simple app

3. Create a Java file and create a custom View

package com.nos;



import android.annotation.*;
import android.content.*;
import android.database.*;
import android.net.*;
import android.os.*;
import android.provider.Settings.*;
import android.text.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import java.io.*;

import android.provider.Settings.System;

public class StatusWeather extends TextView
{
    static final Uri WEATHER_URI = Uri.parse("content://weather/weather");
	private final Context mContext;
	@SuppressLint({"HandlerLeak"})
	private Handler mHandler;
	private final ContentObserver mWeatherObserver;
	private WeatherRunnable mWeatherRunnable;

	public StatusWeather(Context context)
	{
		this(context, null);
	}

	public StatusWeather(Context context, AttributeSet attributeSet)
	{
		this(context, attributeSet, -1);
	}

	public StatusWeather(Context context, AttributeSet attributeSet, int defStyle)
	{
		super(context, attributeSet, defStyle);
		this.mHandler = new WeatherHandler(this);
		this.mWeatherObserver = new StatusWeatherObserver(this.this.mHandler);
		this.mContext = context;
		this.mWeatherRunnable = new WeatherRunnable(this, mHandler);
		setVisibility(this.GONE);
		this.mContext.getContentResolver().registerContentObserver(WEATHER_URI, true.this.mWeatherObserver);
		this.mContext.getContentResolver().registerContentObserver(System.getUriFor("your key"), true.this.mWeatherObserver);
		this.setOnClickListener(new View.OnClickListener(){
				@Override
				public void onClick(View p1)
				{
					Runtime runtime = Runtime.getRuntime();
					try
					{
						runtime.exec("input keyevent 4");
					}
					catch (IOException ignored)
					{
					}
					Intent intent = new Intent();
					intent.setClassName("com.miui.weather2"."com.miui.weather2.ActivityWeatherMain"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); mContext.startActivity(intent); }}); updateWeatherInfo(); }public void updateWeatherInfo(a)
	{
		this.mHandler.removeCallbacks(this.mWeatherRunnable);
		this.mHandler.postDelayed(this.mWeatherRunnable, 200);
	}
	@Override
	protected void onDetachedFromWindow(a)
	{
		super.onDetachedFromWindow();
		if (this.mWeatherObserver ! =null)
		{
			this.mContext.getContentResolver().unregisterContentObserver(this.mWeatherObserver); }}}class WeatherHandler extends Handler
{
	final StatusWeather a;

	WeatherHandler(StatusWeather statusBarWeather)
	{
		this.a = statusBarWeather;
	}
	@Override
	public void handleMessage(Message message)
	{
		String str = (String) message.obj;
		this.a.setText(TextUtils.isEmpty(str) ? "Click for \n Weather data" : str);
		this.a.setVisibility(message.what ! =0 ? 0 : 8); }}class StatusWeatherObserver extends ContentObserver
{
	final StatusWeather mStatusWeather;
	public StatusWeatherObserver(StatusWeather view, Handler handler)
	{
		super(handler);
		this.mStatusWeather = view;
	}
	@Override
	public void onChange(boolean z)
	{ mStatusWeather.updateWeatherInfo(); }}class WeatherRunnable implements Runnable
{
	final StatusWeather this$0;
	final Handler mHandler;
	public WeatherRunnable(StatusWeather weatherView, Handler handler)
	{
		this.this$0 = weatherView;
		this.mHandler = handler;
	}
	@Override
	public void run(a)
	{
		Object obj = "";
		try
		{
			Cursor query = this$0.getContext().getContentResolver().query(StatusWeather.WEATHER_URI, null.null.null.null);
			if(query ! =null)
			{
				if (query.moveToFirst())
				{
					String string = query.getString(query.getColumnIndexOrThrow("city_name"));
					String string2 = query.getString(query.getColumnIndexOrThrow("description"));
					String string3 = query.getString(query.getColumnIndexOrThrow("temperature"));
					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.append(string);
					stringBuilder.append("/");
					stringBuilder.append(string2);
					stringBuilder.append(""); stringBuilder.append(string3); obj = stringBuilder.toString(); } query.close(); }}catch (IllegalArgumentException e)
		{
			obj = "No weather information was obtained.";
		}
		catch (Throwable th)
		{
			Message obtainMessage = mHandler.obtainMessage();
			obtainMessage.what = 100;
			obtainMessage.obj = obj;
			mHandler.sendMessage(obtainMessage);
		}
		Message obtainMessage2 = mHandler.obtainMessage();
		int bb=System.getInt(this.this$0.getContext().getContentResolver(),"your key".0);
		// TODO: Implement this methodobtainMessage2.what = bb; obtainMessage2.obj = obj; mHandler.sendMessage(obtainMessage2); }}Copy the code

StatusWeather is our custom TextView, which is used to get local weather information, and the content observer is used for other applications to control the display and hide of this control

Add to XML

Did not get the weather, but nothing happened, maybe because the app permission is not system level, this is also about a method

Implant part

1. Copy your status bar APK

The following GIF

2. Decompile the status bar

Since my toolbox doesn’t have text writing yet, so I’m using MT Manager, I need to implant this TextView into my dropdown

3. Add code to the XML drop-down

The XML path is RES /layout/quick_status_bar_expanded_header.xml

<?xml version="1.0" encoding="utf-8"? >
<com.android.systemui.qs.QuickStatusBarHeader android:layout_gravity="@integer/notification_panel_layout_gravity" android:id="@id/header" android:background="@android:color/transparent" android:clickable="false" android:clipChildren="false" android:clipToPadding="false" android:layout_width="fill_parent" android:layout_height="@dimen/notch_expanded_header_height" android:baselineAligned="false" android:elevation="4.0 dip"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <com.android.systemui.statusbar.HeaderView android:gravity="bottom" android:layout_gravity="start|bottom|center" android:id="@id/header_content" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginBottom="@dimen/expanded_notification_header_bottom" android:alpha="@dimen/qs_status_bar_header_alpha" android:layout_marginStart="@dimen/expanded_notification_header_start">
        <com.android.systemui.statusbar.policy.Clock android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:gravity="center_vertical" android:id="@id/date_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/carrier_layout" android:layout_alignTop="@id/system_icon_area" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/expanded_notification_weather_temperature_right" />
        <LinearLayout android:id="@id/carrier_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/expanded_notification_weather_temperature_right">
            <com.android.keyguard.CarrierText android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:ellipsize="marquee" android:gravity="center_vertical" android:layout_gravity="center_vertical" android:id="@id/carrier" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="9" android:singleLine="true" android:marqueeRepeatLimit="1" />
        </LinearLayout>
        <LinearLayout android:id="@id/carrier_land_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@id/system_icon_area" android:layout_alignParentBottom="true" android:layout_marginStart="@dimen/notch_expanded_header_carrier_margin" android:layout_toEndOf="@id/date_time">
            <com.android.keyguard.CarrierText android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Weather" android:ellipsize="marquee" android:gravity="center_vertical" android:layout_gravity="center_vertical" android:id="@id/carrier_land" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="9" android:singleLine="true" android:marqueeRepeatLimit="1" />
        </LinearLayout>
        <com.android.keyguard.AlphaOptimizedLinearLayout android:gravity="end" android:orientation="horizontal" android:id="@id/system_icon_area" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toEndOf="@id/date_time" android:layout_alignParentEnd="true">
            <com.nos.Temperature android:textSize="12.0 sp" android:textStyle="bold" android:textColor="#ffffffff" android:gravity="end|center" android:layout_gravity="end|center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/notch_settings_button_margin" />
            <include layout="@layout/system_icons" />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
        <com.android.systemui.statusbar.policy.Clock android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock.Notch" android:id="@id/big_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/date_time" android:layout_alignParentStart="true" />
        <LinearLayout android:orientation="horizontal" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/system_icon_area" android:layout_alignParentEnd="true">
            <com.nos.StatusShortcut android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginBottom="32.0 px" android:layout_marginStart="@dimen/notch_settings_button_margin" android:layout_marginEnd="0.0 dip" />
            <ImageView android:id="@id/notification_shade_shortcut" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/notch_settings_button_margin" android:layout_marginStart="@dimen/notch_settings_button_margin" />
        </LinearLayout>
        <LinearLayout android:orientation="horizontal" android:focusable="true" android:focusableInTouchMode="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30.0 dip" android:layout_above="@id/system_icon_area" android:layout_alignParentTop="true" android:layout_alignParentEnd="true">
            <com.nos.Charge android:textSize="12.0 dip" android:textColor="#ffffffff" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notch_settings_button_margin" />
            <com.nos.StatusWeather android:textSize="12.0 dip" android:textColor="#ffffffff" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/notch_settings_button_margin" />
        </LinearLayout>
    </com.android.systemui.statusbar.HeaderView>
</com.android.systemui.qs.QuickStatusBarHeader>
Copy the code

You can see that there are multiple views embedded here, but they’re all pretty much the same

4. Back to compile

Backcompile takes a long time, click the button behind the folder to see the popup window

5. Replace the XML to the original status bar

You can see that the previous compilation has generated a new APK, which is not available:

  • There is no sign
  • The address was scrambled
  • Switch back to 99% FC

We’re going to have to extract the transformation part of it which is theta

MiuiSystemUI_new.apk/res/layout/quick_status_bar_expanded_header.xml
Copy the code

Replace it with our original status bar APK

It’s not done. It’s not going to find the custom view when it’s replaced back, and the custom view is in our AIDE project, so we’re going to

6. Extract the DEX of the AIDE project

In your own project path, there are build artifacts, apK /class/dex

7. Rename classes.dex to classes2.dex

This step is not detailed operation, set the title in case you miss it

8. Add classes2.dex to the status bar APK

The final result

conclusion

Process of embedding layout (only for non-obfuscated and non-hardened APPS)

  • You need to get your own Dex for Java
  • Decompile the app you want to embed
  • Add the layout to the corresponding XML
  • Backcompile app and extract the corresponding XML
  • Add back the compiled XML to the original APK
  • Add your own dex to the original APK

If the android native dynamic add layout works well, it is possible to add some views by inserting the Android bytecode corresponding to addView