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

Commit 45dde6f1 authored by Evan Severson's avatar Evan Severson
Browse files

Add camera light privacy component

This listens to app op events and turns on all lights of the type CAMERA
when the camera is in use.

Test: atest CameraPrivacyLightControllerTest
Bug: 216107392
Change-Id: I84629bbb8465b20a1298677a42b9214550f9d765
parent b67c420a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -38,6 +38,12 @@ public final class Light implements Parcelable {
    /** Type for lights that indicate microphone usage */
    public static final int LIGHT_TYPE_MICROPHONE = 8;

    /** Type for lights that indicate camera usage
     *
     * @hide
     */
    public static final int LIGHT_TYPE_CAMERA = 9;

    // These enum values start from 10001 to avoid collision with expanding of HAL light types.
    /**
     * Type for lights that indicate a monochrome color LED light.
+3 −0
Original line number Diff line number Diff line
@@ -448,4 +448,7 @@
    <color name="accessibility_magnification_background">#F50D60</color>
    <color name="accessibility_daltonizer_background">#00BCD4</color>
    <color name="accessibility_color_inversion_background">#546E7A</color>

    <!-- Color of camera light when camera is in use -->
    <color name="camera_privacy_light">#FFFFFF</color>
</resources>
+2 −0
Original line number Diff line number Diff line
@@ -4722,4 +4722,6 @@
  <java-symbol type="bool" name="config_lowPowerStandbySupported" />
  <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
  <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />

  <java-symbol type="color" name="camera_privacy_light"/>
</resources>
+125 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensorprivacy;

import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
import android.permission.PermissionManager;
import android.util.ArraySet;

import com.android.internal.R;
import com.android.server.FgThread;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener {

    private final Context mContext;
    private final LightsManager mLightsManager;

    private final Set<String> mActivePackages = new ArraySet<>();
    private final Set<String> mActivePhonePackages = new ArraySet<>();

    private final int mCameraPrivacyLightColor;

    private final List<Light> mCameraLights = new ArrayList<>();
    private final AppOpsManager mAppOpsManager;

    private LightsManager.LightsSession mLightsSession = null;

    CameraPrivacyLightController(Context context) {
        mContext = context;

        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
        mLightsManager = mContext.getSystemService(LightsManager.class);

        mCameraPrivacyLightColor = mContext.getColor(R.color.camera_privacy_light);

        List<Light> lights = mLightsManager.getLights();
        for (int i = 0; i < lights.size(); i++) {
            Light light = lights.get(i);
            if (light.getType() == Light.LIGHT_TYPE_CAMERA) {
                mCameraLights.add(light);
            }
        }

        if (mCameraLights.isEmpty()) {
            return;
        }

        mAppOpsManager.startWatchingActive(
                new String[] {AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_PHONE_CALL_CAMERA},
                FgThread.getExecutor(), this);
    }

    @Override
    public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
        final Set<String> activePackages;
        if (AppOpsManager.OPSTR_CAMERA.equals(op)) {
            activePackages = mActivePackages;
        } else if (AppOpsManager.OPSTR_PHONE_CALL_CAMERA.equals(op)) {
            activePackages = mActivePhonePackages;
        } else {
            return;
        }

        if (active) {
            activePackages.add(packageName);
        } else {
            activePackages.remove(packageName);
        }

        updateLightSession();
    }

    private void updateLightSession() {
        Set<String> exemptedPackages = PermissionManager.getIndicatorExemptedPackages(mContext);

        boolean shouldSessionEnd = exemptedPackages.containsAll(mActivePackages)
                && exemptedPackages.containsAll(mActivePhonePackages);

        if (shouldSessionEnd) {
            if (mLightsSession == null) {
                return;
            }

            mLightsSession.close();
            mLightsSession = null;
        } else {
            if (mLightsSession != null) {
                return;
            }

            LightsRequest.Builder requestBuilder = new LightsRequest.Builder();
            for (int i = 0; i < mCameraLights.size(); i++) {
                requestBuilder.addLight(mCameraLights.get(i),
                        new LightState.Builder()
                                .setColor(mCameraPrivacyLightColor)
                                .build());
            }

            mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE);
            mLightsSession.requestLights(requestBuilder.build());
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -154,6 +154,8 @@ public final class SensorPrivacyService extends SystemService {
    private final AppOpsManagerInternal mAppOpsManagerInternal;
    private final TelephonyManager mTelephonyManager;

    private CameraPrivacyLightController mCameraPrivacyLightController;

    private final IBinder mAppOpsRestrictionToken = new Binder();

    private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal;
@@ -190,6 +192,8 @@ public final class SensorPrivacyService extends SystemService {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
            mEmergencyCallHelper = new EmergencyCallHelper();
        } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
            mCameraPrivacyLightController = new CameraPrivacyLightController(mContext);
        }
    }

Loading