1 /* 2 * Copyright (C) 2018 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.dialer.simulator.service; 18 19 import android.app.ActivityManager; 20 import android.app.ActivityManager.RunningAppProcessInfo; 21 import android.app.Service; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.content.pm.Signature; 28 import android.os.Binder; 29 import android.os.IBinder; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.support.annotation.Nullable; 33 import com.android.dialer.simulator.impl.SimulatorMainPortal; 34 import com.google.common.base.Optional; 35 import com.google.common.collect.ImmutableList; 36 import java.security.MessageDigest; 37 import java.security.NoSuchAlgorithmException; 38 39 /** 40 * A secured android service that gives clients simulator api access through binder if clients do 41 * have registered certificates. 42 */ 43 public class SimulatorService extends Service { 44 45 private static final String POPULATE_DATABASE = "Populate database"; 46 private static final String CLEAN_DATABASE = "Clean database"; 47 private static final String ENABLE_SIMULATOR_MODE = "Enable simulator mode"; 48 private static final String DISABLE_SIMULATOR_MODE = "Disable simulator mode"; 49 private static final String VOICECALL = "VoiceCall"; 50 private static final String NOTIFICATIONS = "Notifications"; 51 private static final String CUSTOMIZED_INCOMING_CALL = "Customized incoming call"; 52 private static final String CUSTOMIZED_OUTGOING_CALL = "Customized outgoing call"; 53 private static final String INCOMING_ENRICHED_CALL = "Incoming enriched call"; 54 private static final String OUTGOING_ENRICHED_CALL = "Outgoing enriched call"; 55 private static final String MISSED_CALL = "Missed calls (few)"; 56 57 // Certificates that used for checking whether a client is a trusted client. 58 // To get a hashed certificate 59 private ImmutableList<String> certificates; 60 61 private SimulatorMainPortal simulatorMainPortal; 62 63 /** 64 * The implementation of {@link ISimulatorService} that contains logic for clients to call 65 * simulator api. 66 */ 67 private final ISimulatorService.Stub binder = 68 new ISimulatorService.Stub() { 69 70 @Override 71 public void makeIncomingCall(String callerId, int presentation) { 72 doSecurityCheck( 73 () -> { 74 simulatorMainPortal.setCallerId(callerId); 75 simulatorMainPortal.setPresentation(presentation); 76 simulatorMainPortal.execute(new String[] {VOICECALL, CUSTOMIZED_INCOMING_CALL}); 77 }); 78 } 79 80 @Override 81 public void makeOutgoingCall(String callerId, int presentation) { 82 doSecurityCheck( 83 () -> { 84 simulatorMainPortal.setCallerId(callerId); 85 simulatorMainPortal.setPresentation(presentation); 86 simulatorMainPortal.execute(new String[] {VOICECALL, CUSTOMIZED_OUTGOING_CALL}); 87 }); 88 } 89 90 @Override 91 public void populateDataBase() throws RemoteException { 92 doSecurityCheck( 93 () -> { 94 simulatorMainPortal.execute(new String[] {POPULATE_DATABASE}); 95 }); 96 } 97 98 @Override 99 public void cleanDataBase() throws RemoteException { 100 doSecurityCheck( 101 () -> { 102 simulatorMainPortal.execute(new String[] {CLEAN_DATABASE}); 103 }); 104 } 105 106 @Override 107 public void enableSimulatorMode() throws RemoteException { 108 doSecurityCheck( 109 () -> { 110 simulatorMainPortal.execute(new String[] {ENABLE_SIMULATOR_MODE}); 111 }); 112 } 113 114 @Override 115 public void disableSimulatorMode() throws RemoteException { 116 doSecurityCheck( 117 () -> { 118 simulatorMainPortal.execute(new String[] {DISABLE_SIMULATOR_MODE}); 119 }); 120 } 121 122 @Override 123 public void makeIncomingEnrichedCall() throws RemoteException { 124 doSecurityCheck( 125 () -> { 126 simulatorMainPortal.execute(new String[] {VOICECALL, INCOMING_ENRICHED_CALL}); 127 }); 128 } 129 130 @Override 131 public void makeOutgoingEnrichedCall() throws RemoteException { 132 doSecurityCheck( 133 () -> { 134 simulatorMainPortal.execute(new String[] {VOICECALL, OUTGOING_ENRICHED_CALL}); 135 }); 136 } 137 138 @Override 139 public void populateMissedCall(int num) throws RemoteException { 140 doSecurityCheck( 141 () -> { 142 simulatorMainPortal.setMissedCallNum(num); 143 simulatorMainPortal.execute(new String[] {NOTIFICATIONS, MISSED_CALL}); 144 }); 145 } 146 147 private void doSecurityCheck(Runnable runnable) { 148 if (!hasAccessToService()) { 149 throw new RuntimeException("Client doesn't have access to Simulator service!"); 150 } 151 runnable.run(); 152 } 153 }; 154 155 /** Sets SimulatorMainPortal instance for SimulatorService. */ setSimulatorMainPortal(SimulatorMainPortal simulatorMainPortal)156 public void setSimulatorMainPortal(SimulatorMainPortal simulatorMainPortal) { 157 this.simulatorMainPortal = simulatorMainPortal; 158 } 159 160 /** Sets immutable CertificatesList for SimulatorService. */ setCertificatesList(ImmutableList<String> certificates)161 public void setCertificatesList(ImmutableList<String> certificates) { 162 this.certificates = certificates; 163 } 164 hasAccessToService()165 private boolean hasAccessToService() { 166 int clientPid = Binder.getCallingPid(); 167 if (clientPid == Process.myPid()) { 168 throw new RuntimeException("Client and service have the same PID!"); 169 } 170 Optional<String> packageName = getPackageNameForPid(clientPid); 171 if (packageName.isPresent()) { 172 try { 173 PackageInfo packageInfo = 174 getPackageManager().getPackageInfo(packageName.get(), PackageManager.GET_SIGNATURES); 175 MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 176 if (packageInfo.signatures.length != 1) { 177 throw new NotOnlyOneSignatureException("The client has more than one signature!"); 178 } 179 Signature signature = packageInfo.signatures[0]; 180 return isCertificateValid(messageDigest.digest(signature.toByteArray()), this.certificates); 181 } catch (NameNotFoundException | NoSuchAlgorithmException | NotOnlyOneSignatureException e) { 182 throw new RuntimeException(e); 183 } 184 } 185 return false; 186 } 187 getPackageNameForPid(int pid)188 private Optional<String> getPackageNameForPid(int pid) { 189 ActivityManager activityManager = 190 (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); 191 for (RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) { 192 if (processInfo.pid == pid) { 193 return Optional.of(processInfo.processName); 194 } 195 } 196 return Optional.absent(); 197 } 198 isCertificateValid( byte[] clientCerfificate, ImmutableList<String> certificates)199 private static boolean isCertificateValid( 200 byte[] clientCerfificate, ImmutableList<String> certificates) { 201 for (String certificate : certificates) { 202 if (certificate.equals(bytesToHexString(clientCerfificate))) { 203 return true; 204 } 205 } 206 return false; 207 } 208 bytesToHexString(byte[] in)209 private static String bytesToHexString(byte[] in) { 210 final StringBuilder builder = new StringBuilder(); 211 for (byte b : in) { 212 builder.append(String.format("%02X", b)); 213 } 214 return builder.toString(); 215 } 216 217 @Nullable 218 @Override onBind(Intent intent)219 public IBinder onBind(Intent intent) { 220 return binder; 221 } 222 223 private static class NotOnlyOneSignatureException extends Exception { NotOnlyOneSignatureException(String desc)224 public NotOnlyOneSignatureException(String desc) { 225 super(desc); 226 } 227 } 228 } 229