| /* |
| * 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.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED; |
| |
| import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN_CAMERA_CHECK; |
| import static com.android.server.rotationresolver.RotationResolverManagerService.RESOLUTION_UNAVAILABLE; |
| import static com.android.server.rotationresolver.RotationResolverManagerService.logRotationStats; |
| |
| import android.Manifest; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.AppGlobals; |
| import android.content.ComponentName; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ServiceInfo; |
| import android.os.CancellationSignal; |
| import android.os.RemoteException; |
| import android.rotationresolver.RotationResolverInternal; |
| import android.service.rotationresolver.RotationResolutionRequest; |
| import android.util.IndentingPrintWriter; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.LatencyTracker; |
| import com.android.server.infra.AbstractPerUserSystemService; |
| |
| /** |
| * Manages the Rotation Resolver Service on a per-user basis. |
| */ |
| final class RotationResolverManagerPerUserService extends |
| AbstractPerUserSystemService<RotationResolverManagerPerUserService, |
| RotationResolverManagerService> { |
| |
| private static final String TAG = RotationResolverManagerPerUserService.class.getSimpleName(); |
| |
| /** Service will unbind if connection is not used for that amount of time. */ |
| private static final long CONNECTION_TTL_MILLIS = 60_000L; |
| |
| @GuardedBy("mLock") |
| @VisibleForTesting |
| RemoteRotationResolverService.RotationRequest mCurrentRequest; |
| |
| @Nullable |
| @VisibleForTesting |
| @GuardedBy("mLock") |
| RemoteRotationResolverService mRemoteService; |
| |
| private ComponentName mComponentName; |
| @GuardedBy("mLock") |
| private LatencyTracker mLatencyTracker; |
| |
| RotationResolverManagerPerUserService(@NonNull RotationResolverManagerService main, |
| @NonNull Object lock, @UserIdInt int userId) { |
| super(main, lock, userId); |
| mLatencyTracker = LatencyTracker.getInstance(getContext()); |
| } |
| |
| @GuardedBy("mLock") |
| void destroyLocked() { |
| if (isVerbose()) { |
| Slog.v(TAG, "destroyLocked()"); |
| } |
| |
| if (mCurrentRequest == null) { |
| return; |
| } |
| Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed."); |
| cancelLocked(); |
| |
| if (mRemoteService != null) { |
| mRemoteService.unbind(); |
| mRemoteService = null; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| @VisibleForTesting |
| void resolveRotationLocked( |
| @NonNull RotationResolverInternal.RotationResolverCallbackInternal callbackInternal, |
| @NonNull RotationResolutionRequest request, |
| @NonNull CancellationSignal cancellationSignalInternal) { |
| |
| if (!isServiceAvailableLocked()) { |
| Slog.w(TAG, "Service is not available at this moment."); |
| callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED); |
| logRotationStats(request.getProposedRotation(), request.getCurrentRotation(), |
| RESOLUTION_UNAVAILABLE); |
| return; |
| } |
| |
| ensureRemoteServiceInitiated(); |
| |
| // Cancel the previous on-going request. |
| if (mCurrentRequest != null && !mCurrentRequest.mIsFulfilled) { |
| cancelLocked(); |
| } |
| |
| synchronized (mLock) { |
| mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN_CAMERA_CHECK); |
| } |
| /** Need to wrap RotationResolverCallbackInternal since there was no other way to hook |
| into the success/failure callback **/ |
| final RotationResolverInternal.RotationResolverCallbackInternal wrapper = |
| new RotationResolverInternal.RotationResolverCallbackInternal() { |
| |
| @Override |
| public void onSuccess(int result) { |
| synchronized (mLock) { |
| mLatencyTracker |
| .onActionEnd(ACTION_ROTATE_SCREEN_CAMERA_CHECK); |
| } |
| callbackInternal.onSuccess(result); |
| } |
| |
| @Override |
| public void onFailure(int error) { |
| synchronized (mLock) { |
| mLatencyTracker |
| .onActionEnd(ACTION_ROTATE_SCREEN_CAMERA_CHECK); |
| } |
| callbackInternal.onFailure(error); |
| } |
| }; |
| mCurrentRequest = new RemoteRotationResolverService.RotationRequest(wrapper, |
| request, cancellationSignalInternal); |
| |
| cancellationSignalInternal.setOnCancelListener(() -> { |
| synchronized (mLock) { |
| if (mCurrentRequest != null && !mCurrentRequest.mIsFulfilled) { |
| Slog.d(TAG, "Trying to cancel the remote request. Reason: Client cancelled."); |
| mCurrentRequest.cancelInternal(); |
| } |
| } |
| }); |
| |
| |
| mRemoteService.resolveRotationLocked(mCurrentRequest); |
| mCurrentRequest.mIsDispatched = true; |
| } |
| |
| @GuardedBy("mLock") |
| private void ensureRemoteServiceInitiated() { |
| if (mRemoteService == null) { |
| mRemoteService = new RemoteRotationResolverService(getContext(), mComponentName, |
| getUserId(), CONNECTION_TTL_MILLIS, mLock); |
| } |
| } |
| |
| /** |
| * get the currently bound component name. |
| */ |
| @VisibleForTesting |
| ComponentName getComponentName() { |
| return mComponentName; |
| } |
| |
| |
| /** Resolves and sets up the rotation resolver service if it had not been done yet. */ |
| @GuardedBy("mLock") |
| @VisibleForTesting |
| boolean isServiceAvailableLocked() { |
| if (mComponentName == null) { |
| mComponentName = updateServiceInfoLocked(); |
| } |
| return mComponentName != null; |
| } |
| |
| @Override // from PerUserSystemService |
| protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) |
| throws PackageManager.NameNotFoundException { |
| ServiceInfo serviceInfo; |
| try { |
| serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, |
| PackageManager.GET_META_DATA, mUserId); |
| if (serviceInfo != null) { |
| final String permission = serviceInfo.permission; |
| if (!Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE.equals(permission)) { |
| throw new SecurityException(String.format( |
| "Service %s requires %s permission. Found %s permission", |
| serviceInfo.getComponentName(), |
| Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE, |
| serviceInfo.permission)); |
| } |
| } |
| } catch (RemoteException e) { |
| throw new PackageManager.NameNotFoundException( |
| "Could not get service for " + serviceComponent); |
| } |
| return serviceInfo; |
| } |
| |
| @GuardedBy("mLock") |
| private void cancelLocked() { |
| if (mCurrentRequest == null) { |
| return; |
| } |
| mCurrentRequest.cancelInternal(); |
| mCurrentRequest = null; |
| } |
| |
| void dumpInternal(IndentingPrintWriter ipw) { |
| synchronized (mLock) { |
| if (mRemoteService != null) { |
| mRemoteService.dump("", ipw); |
| } |
| if (mCurrentRequest != null) { |
| mCurrentRequest.dump(ipw); |
| } |
| } |
| } |
| } |