Android 10.0 (q) AOSP adds application lock function

preface

The function of applying locks can be said to be very common, which is to intercept the corresponding code of startActivity.

At the beginning, I found some information on the Internet, but there was nothing suitable for direct use, so I did it myself. Here is a simple note.

Android application lock implementation

Then give everyone an effect picture first

Train of thought analysis

Since our target application is system Settings, this guy's portal is not unique. At first, he thought of intercepting in Launcher3,

The final effect is not perfect. Later, it was changed to startActivity() in ActivityStarter

How did you find this place? Because I changed one before Activity cannot be pulled up in the background above version Q What was wrong with the report at that time

Right here, I thought later that the next error report was intercepted, so we can also customize the password here to intercept.

1. Find the intercept start location, startActivity()

2. Read the package name corresponding to the Activity to be pulled up and the APP package name to be locked

3. In the lock list, the verification interface pops up. The verification is successful. Continue to execute startActivity

4. Direct release without locking

Upper code

1. Interceptable places in Launcher3

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java

public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
            @Nullable String sourceContainer) {
        if (TestProtocol.sDebugTracing) {
            android.util.Log.d(TestProtocol.NO_START_TAG,
                    "startActivitySafely outer");
        }

         //cczheng add testcode
        final String packageName = intent.getComponent().getPackageName();
        android.util.Log.i("ActivityStarter111","packageName="+packageName);
        if ("com.android.settings".equals(packageName)) {
          //android.widget.Toast.makeText(this, "foo", 1000).show();
          //return true;
        }//end

        if (!hasBeenResumed()) {
            // Workaround an issue where the WM launch animation is clobbered when finishing the
            // recents animation into launcher. Defer launching the activity until Launcher is
            // next resumed.
            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
            return true;
        }

        boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
        if (success && v instanceof BubbleTextView) {
            // This is set to the view that launched the activity that navigated the user away
            // from launcher. Since there is no callback for when the activity has finished
            // launching, enable the press state and keep this reference to reset the press
            // state when we return to launcher.
            BubbleTextView btv = (BubbleTextView) v;
            btv.setStayPressed(true);
            addOnResumeCallback(btv);
        }
        return success;
    }

After judging the package name here, you can directly interact with the new dialog box. It has the advantages of convenience and simplicity, but it cannot be locked completely.

2. Interceptable places in ActivityStarter

I'm lazy here. There's nothing to beautify the interface. You can adjust it as needed. It is estimated that there will be problems with the introduction of R resources, so it is directly here

The layout is written in java code and displayed in window mode.

The list of package names to be intercepted can be queried through the ContentProvider. After all, there is a Context and you can add it as needed.

frameworks\base\services\core\java\com\android\server\wm\ActivityStarter.java

import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.InputType;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;


    //cczheng add for appLockPass S
    boolean isLocked;
    boolean isPassCheck;
    private void showAppLockPasswordWindow(Context mContext, IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {

        final WindowManager.LayoutParams params =  new WindowManager.LayoutParams();
        params.width = 380;
        params.height = 230;
        params.format = Color.parseColor("#ADADAD");
        params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
        params.setTitle("AppLock");
        WindowManager mWM = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

        final LinearLayout parentLayout = new LinearLayout(mContext);
        parentLayout.setOrientation(LinearLayout.VERTICAL);
        parentLayout.setBackgroundColor(Color.WHITE);
        LinearLayout.LayoutParams layoutParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
        parentLayout.setLayoutParams(layoutParams);

        TextView titleText = new TextView(mContext);
        LinearLayout.LayoutParams contentParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        titleText.setLayoutParams(contentParams);
        titleText.setText("Please enter app password");
        titleText.setTextColor(Color.BLACK);
        titleText.setTypeface(Typeface.create(titleText.getTypeface(), Typeface.NORMAL), Typeface.BOLD);
        titleText.setPadding(10, 10, 0, 0);
        parentLayout.addView(titleText);

        EditText passText = new EditText(mContext);
        passText.setLayoutParams(contentParams);
        passText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        passText.setTextColor(Color.BLACK);
        parentLayout.addView(passText);

        Button okBtn = new Button(mContext);
        LinearLayout.LayoutParams btnParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        btnParams.gravity = Gravity.RIGHT;
        okBtn.setLayoutParams(btnParams);
        okBtn.setBackgroundColor(Color.TRANSPARENT);
        okBtn.setText("Confirm");
        okBtn.setTextColor(Color.parseColor("#3996E8"));
        okBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String password = passText.getText().toString();
                //android.widget.Toast.makeText(mContext, password, 1000).show();
                if (parentLayout!=null){
                    mWM.removeViewImmediate(parentLayout);
                    //parentLayout = null;
                }
       
                if ("123".equals(password)) {
                    isPassCheck = true;
                    startActivity(caller, intent, ephemeralIntent,
                             resolvedType, aInfo, rInfo,
                             voiceSession,  voiceInteractor,
                             resultTo,  resultWho,  requestCode,  callingPid,  callingUid,
                             callingPackage,  realCallingPid,  realCallingUid,  startFlags,
                             options,
                             ignoreTargetSecurity,  componentSpecified,  outActivity,
                             inTask,  allowPendingRemoteAnimationRegistryLookup,
                             originatingPendingIntent,  allowBackgroundActivityStart);
                }else {
                    isPassCheck = false;
                }
            }
        });
        parentLayout.addView(okBtn);

        try {
            mWM.addView(parentLayout, params);
        } catch (WindowManager.BadTokenException e) {
            e.printStackTrace();
        }
    }

    IApplicationThread mscaller; 
    Intent msintent, msephemeralIntent;
    String msresolvedType, msresultWho, mscallingPackage;
    ActivityInfo msaInfo;
    ResolveInfo msrInfo;
    int msrequestCode, mscallingPid, mscallingUid, msrealCallingPid, msrealCallingUid, msstartFlags;
    boolean msignoreTargetSecurity, mscomponentSpecified, msallowPendingRemoteAnimationRegistryLookup, msallowBackgroundActivityStart;
    IVoiceInteractionSession msvoiceSession;
    IVoiceInteractor msvoiceInteractor;
    IBinder msresultTo;
    SafeActivityOptions msoptions;
    ActivityRecord[] msoutActivity;
    TaskRecord msinTask;
    PendingIntentRecord msoriginatingPendingIntent;
    //E

    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
        mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
        int err = ActivityManager.START_SUCCESS;
        // Pull the optional Ephemeral Installer-only bundle out of the options early.
        final Bundle verificationBundle
                = options != null ? options.popAppVerificationBundle() : null;

	    ................................
		
		checkedOptions = mInterceptor.mActivityOptions;
        }
        
        //cczheng add for appLockPass S
        android.util.Log.e("ActivityStarter","callingPackage="+callingPackage);
        final String packageName = intent.getComponent().getPackageName();
        android.util.Log.i("ActivityStarter","packageName="+packageName+" abort="+abort);
  
		if (("com.android.launcher3".equals(callingPackage) || "com.android.systemui".equals(callingPackage)) 
				&& "com.android.settings".equals(packageName)) {
			isLocked = true;
		}else{
			isLocked = false;
		}

		if (isPassCheck) {
			android.util.Log.i("ActivityStarter","isPassCheck pass goto activity");
			isLocked = false;
			isPassCheck = false;
		}

      mscaller = caller;
      msintent = intent;
      msephemeralIntent = ephemeralIntent;
      msresolvedType = resolvedType;
      msaInfo = aInfo;
      msvoiceSession = voiceSession;
      msvoiceInteractor = voiceInteractor;
      msresultTo = resultTo;
      msresultWho = resultWho;
      msrequestCode = requestCode;
      mscallingPid = callingPid;
      mscallingUid = callingUid;
      mscallingPackage = callingPackage;
      msrealCallingPid = realCallingPid;
      msrealCallingUid = realCallingUid;
      msstartFlags = startFlags;
      msoptions = options;
      msignoreTargetSecurity = ignoreTargetSecurity;
      mscomponentSpecified = componentSpecified;
      msoutActivity = outActivity;
      msinTask = inTask;
      msallowPendingRemoteAnimationRegistryLookup = allowPendingRemoteAnimationRegistryLookup;
      msoriginatingPendingIntent = originatingPendingIntent;
      msallowBackgroundActivityStart = allowBackgroundActivityStart;

      new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
                if (isLocked) {
                    showAppLockPasswordWindow(mService.mContext, mscaller, msintent, msephemeralIntent,
                             msresolvedType, msaInfo, msrInfo,
                             msvoiceSession,  msvoiceInteractor,
                             msresultTo,  msresultWho,  msrequestCode,  mscallingPid,  mscallingUid,
                             mscallingPackage,  msrealCallingPid,  msrealCallingUid,  msstartFlags,
                             msoptions,
                             msignoreTargetSecurity,  mscomponentSpecified,  msoutActivity,
                             msinTask,  msallowPendingRemoteAnimationRegistryLookup,
                             msoriginatingPendingIntent,  msallowBackgroundActivityStart);
                }
            }
        });

       if (isLocked) {
           android.util.Log.d("ActivityStarter","START_SWITCHES_CANCELED=");
          return ActivityManager.START_SWITCHES_CANCELED;
       }///add EE

        if (abort) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
                        RESULT_CANCELED, null);
            }
            // We pretend to the caller that it was really started, but
            // they will just get a cancel result.
            ActivityOptions.abort(checkedOptions);
            return START_ABORTED;
        }
		
		
		........................


Let's talk about the above points

The callingPackage is the initiator calling the startActivity method. You can see that I have added filtering here. It must be SystemUI and Launcher3

Intercept only when called. When the system is started, we see that Android is starting, that is, FallbackHome in Settings. At this time, the callingPackage is null

This will get stuck in the system, so it needs to be filtered.

showAppLockPasswordWindow() needs to be executed in the Handler. UI thread operation cannot be performed directly in ActivityStarter, otherwise an error will be reported.

When the interception conditions are met, you need to return the startActivity first, asynchronously display the UI operation, and recurse the startActivity again according to the password result

Therefore, showAppLockPasswordWindow() is placed in the internal class, and the transfer parameters need to be final or global variables. Therefore, a lot of global assignments are added above, and there are too many parameters.

Tags: Android

Posted on Fri, 03 Dec 2021 11:11:56 -0500 by msing