1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.musicrecognition; 18 19 import static android.Manifest.permission.MANAGE_MUSIC_RECOGNITION; 20 import static android.content.PermissionChecker.PERMISSION_GRANTED; 21 import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.media.musicrecognition.IMusicRecognitionManager; 29 import android.media.musicrecognition.IMusicRecognitionManagerCallback; 30 import android.media.musicrecognition.RecognitionRequest; 31 import android.os.Binder; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.os.ResultReceiver; 35 import android.os.ShellCallback; 36 import android.os.UserHandle; 37 import android.util.Slog; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.server.infra.AbstractMasterSystemService; 41 import com.android.server.infra.FrameworkResourcesServiceNameResolver; 42 43 import java.io.FileDescriptor; 44 import java.util.concurrent.ExecutorService; 45 import java.util.concurrent.Executors; 46 47 /** 48 * Service which allows audio to be securely streamed to a designated {@link 49 * MusicRecognitionService}. 50 */ 51 public class MusicRecognitionManagerService extends 52 AbstractMasterSystemService<MusicRecognitionManagerService, 53 MusicRecognitionManagerPerUserService> { 54 55 private static final String TAG = MusicRecognitionManagerService.class.getSimpleName(); 56 private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 60_000; 57 58 private MusicRecognitionManagerStub mMusicRecognitionManagerStub; 59 final ExecutorService mExecutorService = Executors.newCachedThreadPool(); 60 61 /** 62 * Initializes the system service. 63 * 64 * Subclasses must define a single argument constructor that accepts the context 65 * and passes it to super. 66 * 67 * @param context The system server context. 68 */ MusicRecognitionManagerService(@onNull Context context)69 public MusicRecognitionManagerService(@NonNull Context context) { 70 super(context, new FrameworkResourcesServiceNameResolver(context, 71 com.android.internal.R.string.config_defaultMusicRecognitionService), 72 /** disallowProperty */null); 73 } 74 75 @Nullable 76 @Override newServiceLocked(int resolvedUserId, boolean disabled)77 protected MusicRecognitionManagerPerUserService newServiceLocked(int resolvedUserId, 78 boolean disabled) { 79 return new MusicRecognitionManagerPerUserService(this, mLock, resolvedUserId); 80 } 81 82 @Override onStart()83 public void onStart() { 84 mMusicRecognitionManagerStub = new MusicRecognitionManagerStub(); 85 publishBinderService(Context.MUSIC_RECOGNITION_SERVICE, mMusicRecognitionManagerStub); 86 } 87 enforceCaller(String func)88 private void enforceCaller(String func) { 89 Context ctx = getContext(); 90 if (ctx.checkCallingPermission(android.Manifest.permission.MANAGE_MUSIC_RECOGNITION) 91 == PERMISSION_GRANTED) { 92 return; 93 } 94 95 String msg = "Permission Denial: " + func + " from pid=" 96 + Binder.getCallingPid() 97 + ", uid=" + Binder.getCallingUid() 98 + " doesn't hold " + android.Manifest.permission.MANAGE_MUSIC_RECOGNITION; 99 throw new SecurityException(msg); 100 } 101 102 @Override enforceCallingPermissionForManagement()103 protected void enforceCallingPermissionForManagement() { 104 getContext().enforceCallingPermission(MANAGE_MUSIC_RECOGNITION, TAG); 105 } 106 107 @Override getMaximumTemporaryServiceDurationMs()108 protected int getMaximumTemporaryServiceDurationMs() { 109 return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS; 110 } 111 112 final class MusicRecognitionManagerStub extends IMusicRecognitionManager.Stub { 113 @Override beginRecognition( @onNull RecognitionRequest recognitionRequest, @NonNull IBinder callback)114 public void beginRecognition( 115 @NonNull RecognitionRequest recognitionRequest, 116 @NonNull IBinder callback) { 117 enforceCaller("beginRecognition"); 118 119 synchronized (mLock) { 120 int userId = UserHandle.getCallingUserId(); 121 final MusicRecognitionManagerPerUserService service = getServiceForUserLocked( 122 userId); 123 if (service != null && (isDefaultServiceLocked(userId) 124 || isCalledByServiceAppLocked("beginRecognition"))) { 125 service.beginRecognitionLocked(recognitionRequest, callback); 126 } else { 127 try { 128 IMusicRecognitionManagerCallback.Stub.asInterface(callback) 129 .onRecognitionFailed(RECOGNITION_FAILED_SERVICE_UNAVAILABLE); 130 } catch (RemoteException e) { 131 // ignored. 132 } 133 } 134 } 135 } 136 137 @Override onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver)138 public void onShellCommand(@Nullable FileDescriptor in, 139 @Nullable FileDescriptor out, 140 @Nullable FileDescriptor err, 141 @NonNull String[] args, 142 @Nullable ShellCallback callback, 143 @NonNull ResultReceiver resultReceiver) throws RemoteException { 144 new MusicRecognitionManagerServiceShellCommand( 145 MusicRecognitionManagerService.this).exec(this, in, out, err, args, callback, 146 resultReceiver); 147 } 148 149 /** True if the currently set handler service is not overridden by the shell. */ 150 @GuardedBy("mLock") isDefaultServiceLocked(int userId)151 private boolean isDefaultServiceLocked(int userId) { 152 final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId); 153 if (defaultServiceName == null) { 154 return false; 155 } 156 157 final String currentServiceName = mServiceNameResolver.getServiceName(userId); 158 return defaultServiceName.equals(currentServiceName); 159 } 160 161 /** True if the caller of the api is the same app which hosts the default service. */ 162 @GuardedBy("mLock") isCalledByServiceAppLocked(@onNull String methodName)163 private boolean isCalledByServiceAppLocked(@NonNull String methodName) { 164 final int userId = UserHandle.getCallingUserId(); 165 final int callingUid = Binder.getCallingUid(); 166 final String serviceName = mServiceNameResolver.getServiceName(userId); 167 if (serviceName == null) { 168 Slog.e(TAG, methodName + ": called by UID " + callingUid 169 + ", but there's no service set for user " + userId); 170 return false; 171 } 172 173 final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); 174 if (serviceComponent == null) { 175 Slog.w(TAG, methodName + ": invalid service name: " + serviceName); 176 return false; 177 } 178 179 final String servicePackageName = serviceComponent.getPackageName(); 180 181 final PackageManager pm = getContext().getPackageManager(); 182 final int serviceUid; 183 try { 184 serviceUid = pm.getPackageUidAsUser(servicePackageName, 185 UserHandle.getCallingUserId()); 186 } catch (PackageManager.NameNotFoundException e) { 187 Slog.w(TAG, methodName + ": could not verify UID for " + serviceName); 188 return false; 189 } 190 if (callingUid != serviceUid) { 191 Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is " 192 + serviceUid); 193 return false; 194 } 195 196 return true; 197 } 198 } 199 } 200