Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb5aa240 authored by Wenhao Wang's avatar Wenhao Wang
Browse files

Init forensic service

This CL adds the forensic service.
It includes a BinderService, which can be used to do the following:
1. register a callback to register the state of the forensic service.
2. change the state of the forensic service.
  2.a. make it visible.
  2.b. make it invisible.
  2.c. enable it.
  2.d. disable it.

Bug: 365994454
Test: atest ForensicServiceTest
Flag: android.security.afl_api
Ignore-AOSP-First: security feature
Change-Id: Ida83d19d158723bca36fcd022f0b7fe627d4fe6a
parent dc12d6ea
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -5657,6 +5657,14 @@ public abstract class Context {
    @SuppressLint("ServiceName")
    public static final String BINARY_TRANSPARENCY_SERVICE = "transparency";

    /**
     * System service name for ForensicService.
     * The service manages the forensic info on device.
     * @hide
     */
    @FlaggedApi(android.security.Flags.FLAG_AFL_API)
    public static final String FORENSIC_SERVICE = "forensic";

    /**
     * System service name for the DeviceIdleManager.
     * @see #getSystemService(String)
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.security.forensic;

import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;

/**
 * Binder interface to communicate with ForensicService.
 * @hide
 */
interface IForensicService {
    void monitorState(IForensicServiceStateCallback callback);
    void makeVisible(IForensicServiceCommandCallback callback);
    void makeInvisible(IForensicServiceCommandCallback callback);
    void enable(IForensicServiceCommandCallback callback);
    void disable(IForensicServiceCommandCallback callback);
}
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.security.forensic;

/**
 * @hide
 */
 oneway interface IForensicServiceCommandCallback {
     @Backing(type="int")
     enum ErrorCode{
         UNKNOWN = 0,
         PERMISSION_DENIED = 1,
         INVALID_STATE_TRANSITION = 2,
         BACKUP_TRANSPORT_UNAVAILABLE = 3,
         DATA_SOURCE_UNAVAILABLE = 3,
     }
    void onSuccess();
    void onFailure(ErrorCode error);
 }
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.security.forensic;

/**
 * @hide
 */
 oneway interface IForensicServiceStateCallback {
    @Backing(type="int")
    enum State{
        UNKNOWN = 0,
        INVISIBLE = 1,
        VISIBLE = 2,
        ENABLED = 3,
    }
    void onStateChange(State state);
 }
+294 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.security.forensic;

import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.security.forensic.IForensicService;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
import com.android.server.SystemService;

import java.util.ArrayList;

/**
 * @hide
 */
public class ForensicService extends SystemService {
    private static final String TAG = "ForensicService";

    private static final int MSG_MONITOR_STATE = 0;
    private static final int MSG_MAKE_VISIBLE = 1;
    private static final int MSG_MAKE_INVISIBLE = 2;
    private static final int MSG_ENABLE = 3;
    private static final int MSG_DISABLE = 4;
    private static final int MSG_BACKUP = 5;

    private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
    private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
    private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE;
    private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;

    private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
    private static final int ERROR_PERMISSION_DENIED =
            IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
    private static final int ERROR_INVALID_STATE_TRANSITION =
            IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
    private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE =
            IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE;
    private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
            IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;

    private final Context mContext;
    private final Handler mHandler;
    private final BinderService mBinderService;

    private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
    private volatile int mState = STATE_INVISIBLE;

    public ForensicService(@NonNull Context context) {
        this(new InjectorImpl(context));
    }

    @VisibleForTesting
    ForensicService(@NonNull Injector injector) {
        super(injector.getContext());
        mContext = injector.getContext();
        mHandler = new EventHandler(injector.getLooper(), this);
        mBinderService = new BinderService(this);
    }

    @VisibleForTesting
    protected void setState(int state) {
        mState = state;
    }

    private static final class BinderService extends IForensicService.Stub {
        final ForensicService mService;

        BinderService(ForensicService service)  {
            mService = service;
        }

        @Override
        public void monitorState(IForensicServiceStateCallback callback) {
            mService.mHandler.obtainMessage(MSG_MONITOR_STATE, callback).sendToTarget();
        }

        @Override
        public void makeVisible(IForensicServiceCommandCallback callback) {
            mService.mHandler.obtainMessage(MSG_MAKE_VISIBLE, callback).sendToTarget();
        }

        @Override
        public void makeInvisible(IForensicServiceCommandCallback callback) {
            mService.mHandler.obtainMessage(MSG_MAKE_INVISIBLE, callback).sendToTarget();
        }

        @Override
        public void enable(IForensicServiceCommandCallback callback) {
            mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget();
        }

        @Override
        public void disable(IForensicServiceCommandCallback callback) {
            mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget();
        }
    }

    private static class EventHandler extends Handler {
        private final ForensicService mService;

        EventHandler(Looper looper, ForensicService service) {
            super(looper);
            mService = service;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_MONITOR_STATE:
                    try {
                        mService.monitorState(
                                (IForensicServiceStateCallback) msg.obj);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "RemoteException", e);
                    }
                    break;
                case MSG_MAKE_VISIBLE:
                    try {
                        mService.makeVisible((IForensicServiceCommandCallback) msg.obj);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "RemoteException", e);
                    }
                    break;
                case MSG_MAKE_INVISIBLE:
                    try {
                        mService.makeInvisible((IForensicServiceCommandCallback) msg.obj);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "RemoteException", e);
                    }
                    break;
                case MSG_ENABLE:
                    try {
                        mService.enable((IForensicServiceCommandCallback) msg.obj);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "RemoteException", e);
                    }
                    break;
                case MSG_DISABLE:
                    try {
                        mService.disable((IForensicServiceCommandCallback) msg.obj);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "RemoteException", e);
                    }
                    break;
                default:
                    Slog.w(TAG, "Unknown message: " + msg.what);
            }
        }
    }

    private void monitorState(IForensicServiceStateCallback callback) throws RemoteException {
        for (int i = 0; i < mStateMonitors.size(); i++) {
            if (mStateMonitors.get(i).asBinder() == callback.asBinder()) {
                return;
            }
        }
        mStateMonitors.add(callback);
        callback.onStateChange(mState);
    }

    private void notifyStateMonitors() throws RemoteException {
        for (int i = 0; i < mStateMonitors.size(); i++) {
            mStateMonitors.get(i).onStateChange(mState);
        }
    }

    private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
        switch (mState) {
            case STATE_INVISIBLE:
                mState = STATE_VISIBLE;
                notifyStateMonitors();
                callback.onSuccess();
                break;
            case STATE_VISIBLE:
                callback.onSuccess();
                break;
            default:
                callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
        }
    }

    private void makeInvisible(IForensicServiceCommandCallback callback) throws RemoteException {
        switch (mState) {
            case STATE_VISIBLE:
            case STATE_ENABLED:
                mState = STATE_INVISIBLE;
                notifyStateMonitors();
                callback.onSuccess();
                break;
            case STATE_INVISIBLE:
                callback.onSuccess();
                break;
            default:
                callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
        }
    }

    private void enable(IForensicServiceCommandCallback callback) throws RemoteException {
        switch (mState) {
            case STATE_VISIBLE:
                mState = STATE_ENABLED;
                notifyStateMonitors();
                callback.onSuccess();
                break;
            case STATE_ENABLED:
                callback.onSuccess();
                break;
            default:
                callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
        }
    }

    private void disable(IForensicServiceCommandCallback callback) throws RemoteException {
        switch (mState) {
            case STATE_ENABLED:
                mState = STATE_VISIBLE;
                notifyStateMonitors();
                callback.onSuccess();
                break;
            case STATE_VISIBLE:
                callback.onSuccess();
                break;
            default:
                callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
        }
    }

    @Override
    public void onStart() {
        try {
            publishBinderService(Context.FORENSIC_SERVICE, mBinderService);
        } catch (Throwable t) {
            Slog.e(TAG, "Could not start the ForensicService.", t);
        }
    }

    @VisibleForTesting
    IForensicService getBinderService() {
        return mBinderService;
    }

    interface Injector {
        Context getContext();

        Looper getLooper();
    }

    private static final class InjectorImpl implements Injector {
        private final Context mContext;

        InjectorImpl(Context context) {
            mContext = context;
        }

        @Override
        public Context getContext() {
            return mContext;
        }


        @Override
        public Looper getLooper() {
            ServiceThread serviceThread =
                    new ServiceThread(
                            TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
            serviceThread.start();
            return serviceThread.getLooper();
        }
    }
}
Loading