blob: 19a246ef0ee5f9f1a2cf84ca71d037eea7a1cc29 [file] [log] [blame]
/*
* Copyright (C) 2020 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.rotationresolver;
import static android.provider.DeviceConfig.NAMESPACE_ROTATION_RESOLVER;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_NOT_SUPPORTED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_PREEMPTED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_TIMED_OUT;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.rotationresolver.RotationResolverInternal;
import android.service.rotationresolver.RotationResolutionRequest;
import android.service.rotationresolver.RotationResolverService;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.Set;
/**
* A manager service for rotation resolver service that runs in System Server process.
* This service publishes a LocalService and reroutes calls to a {@link
* android.service.rotationresolver.RotationResolverService} that it manages.
*/
public class RotationResolverManagerService extends
AbstractMasterSystemService<RotationResolverManagerService,
RotationResolverManagerPerUserService> {
private static final String TAG = RotationResolverManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
static final int ORIENTATION_UNKNOWN =
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
static final int RESOLUTION_DISABLED =
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__DISABLED;
static final int RESOLUTION_UNAVAILABLE =
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNAVAILABLE;
static final int RESOLUTION_FAILURE =
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
private final Context mContext;
private final SensorPrivacyManager mPrivacyManager;
boolean mIsServiceEnabled;
public RotationResolverManagerService(Context context) {
super(context,
new FrameworkResourcesServiceNameResolver(
context,
R.string.config_defaultRotationResolverService), /*disallowProperty=*/null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
mPrivacyManager = SensorPrivacyManager.getInstance(context);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ROTATION_RESOLVER,
getContext().getMainExecutor(),
(properties) -> onDeviceConfigChange(properties.getKeyset()));
mIsServiceEnabled = DeviceConfig.getBoolean(NAMESPACE_ROTATION_RESOLVER,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
}
private void onDeviceConfigChange(@NonNull Set<String> keys) {
if (keys.contains(KEY_SERVICE_ENABLED)) {
mIsServiceEnabled = DeviceConfig.getBoolean(NAMESPACE_ROTATION_RESOLVER,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
}
@Override
public void onStart() {
publishBinderService(Context.ROTATION_RESOLVER_SERVICE, new BinderService());
publishLocalService(RotationResolverInternal.class, new LocalService());
}
@Override
public RotationResolverManagerPerUserService newServiceLocked(
@UserIdInt int resolvedUserId,
@SuppressWarnings("unused") boolean disabled) {
return new RotationResolverManagerPerUserService(this, mLock, resolvedUserId);
}
@Override
protected void onServiceRemoved(
RotationResolverManagerPerUserService service, @UserIdInt int userId) {
synchronized (mLock) {
service.destroyLocked();
}
}
/** Returns {@code true} if rotation resolver service is configured on this device. */
public static boolean isServiceConfigured(Context context) {
return !TextUtils.isEmpty(getServiceConfigPackage(context));
}
static String getServiceConfigPackage(Context context) {
return context.getPackageManager().getRotationResolverPackageName();
}
private final class LocalService extends RotationResolverInternal {
@Override
public boolean isRotationResolverSupported() {
synchronized (mLock) {
return mIsServiceEnabled;
}
}
@Override
public void resolveRotation(
@NonNull RotationResolverCallbackInternal callbackInternal, String packageName,
int proposedRotation, int currentRotation, long timeout,
@NonNull CancellationSignal cancellationSignalInternal) {
Objects.requireNonNull(callbackInternal);
Objects.requireNonNull(cancellationSignalInternal);
synchronized (mLock) {
final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
SensorPrivacyManager.Sensors.CAMERA);
if (mIsServiceEnabled && isCameraAvailable) {
final RotationResolverManagerPerUserService service =
getServiceForUserLocked(
UserHandle.getCallingUserId());
final RotationResolutionRequest request;
if (packageName == null) {
request = new RotationResolutionRequest(/* packageName */ "",
currentRotation, proposedRotation, /* shouldUseCamera */ true,
timeout);
} else {
request = new RotationResolutionRequest(packageName, currentRotation,
proposedRotation, /* shouldUseCamera */ true, timeout);
}
service.resolveRotationLocked(callbackInternal, request,
cancellationSignalInternal);
} else {
if (isCameraAvailable) {
Slog.w(TAG, "Rotation Resolver service is disabled.");
} else {
Slog.w(TAG, "Camera is locked by a toggle.");
}
callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
}
}
}
}
private final class BinderService extends Binder {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
return;
}
synchronized (mLock) {
final RotationResolverManagerPerUserService service = getServiceForUserLocked(
UserHandle.getCallingUserId());
service.dumpInternal(new IndentingPrintWriter(pw, " "));
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err,
String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROTATION_RESOLVER,
TAG);
final RotationResolverManagerPerUserService service = getServiceForUserLocked(
UserHandle.getCallingUserId());
new RotationResolverShellCommand(service).exec(this, in, out, err, args, callback,
resultReceiver);
}
}
static void logRotationStatsWithTimeToCalculate(int proposedRotation, int currentRotation,
int result, long timeToCalculate) {
FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
/* previous_orientation= */ surfaceRotationToProto(currentRotation),
/* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
result,
/* process_duration_millis= */ timeToCalculate);
}
static void logRotationStats(int proposedRotation, int currentRotation,
int result) {
FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
/* previous_orientation= */ surfaceRotationToProto(currentRotation),
/* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
result);
}
static int errorCodeToProto(@RotationResolverService.FailureCodes int error) {
switch (error) {
case ROTATION_RESULT_FAILURE_NOT_SUPPORTED:
return RESOLUTION_UNAVAILABLE;
case ROTATION_RESULT_FAILURE_TIMED_OUT:
case ROTATION_RESULT_FAILURE_PREEMPTED:
case ROTATION_RESULT_FAILURE_CANCELLED:
return ORIENTATION_UNKNOWN;
default:
return RESOLUTION_FAILURE;
}
}
static int surfaceRotationToProto(@Surface.Rotation int rotationPoseResult) {
switch (rotationPoseResult) {
case Surface.ROTATION_0:
return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
case Surface.ROTATION_90:
return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;
case Surface.ROTATION_180:
return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
case Surface.ROTATION_270:
return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
default:
// Should not reach here.
return RESOLUTION_FAILURE;
}
}
}