From 4bf1692c60c7653598a3ad1d898dc2e139974617 Mon Sep 17 00:00:00 2001 From: weijiaxu Date: Fri, 22 Jun 2018 12:52:21 -0700 Subject: Add Simulator service. Add Simulator service so that trusted clients can access to dialer simulator apis. Bug: 79488174 Test: included tests. PiperOrigin-RevId: 201727670 Change-Id: Ic4ebb256d178e03f1de98a4e40a2b02efd3a9620 --- .../dialer/simulator/impl/AndroidManifest.xml | 15 ++ .../dialer/simulator/impl/SimulatorMainPortal.java | 78 ++++++- .../dialer/simulator/impl/SimulatorVoiceCall.java | 20 +- .../dialer/simulator/service/AndroidManifest.xml | 29 +++ .../simulator/service/ISimulatorService.aidl | 45 ++++ .../dialer/simulator/service/SimulatorService.java | 228 +++++++++++++++++++++ .../simulator/service/SimulatorServiceClient.java | 70 +++++++ 7 files changed, 474 insertions(+), 11 deletions(-) create mode 100644 java/com/android/dialer/simulator/service/AndroidManifest.xml create mode 100644 java/com/android/dialer/simulator/service/ISimulatorService.aidl create mode 100644 java/com/android/dialer/simulator/service/SimulatorService.java create mode 100644 java/com/android/dialer/simulator/service/SimulatorServiceClient.java (limited to 'java') diff --git a/java/com/android/dialer/simulator/impl/AndroidManifest.xml b/java/com/android/dialer/simulator/impl/AndroidManifest.xml index a30504d3f..718d50e1d 100644 --- a/java/com/android/dialer/simulator/impl/AndroidManifest.xml +++ b/java/com/android/dialer/simulator/impl/AndroidManifest.xml @@ -1,4 +1,19 @@ + diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainPortal.java b/java/com/android/dialer/simulator/impl/SimulatorMainPortal.java index 273826f70..261275756 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorMainPortal.java +++ b/java/com/android/dialer/simulator/impl/SimulatorMainPortal.java @@ -18,19 +18,25 @@ package com.android.dialer.simulator.impl; import android.content.Context; import android.support.v7.app.AppCompatActivity; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.view.ActionProvider; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity; import com.android.dialer.simulator.Simulator; import com.android.dialer.simulator.SimulatorComponent; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ListenableFuture; /** Implements the top level simulator menu. */ -public final class SimulatorMainPortal { +public class SimulatorMainPortal { private final Context context; private final AppCompatActivity activity; - private SimulatorPortalEntryGroup simulatorMainPortal; + private SimulatorPortalEntryGroup simulatorPortalEntryGroup; + private String callerId = ""; + private int presentation = TelecomManager.PRESENTATION_ALLOWED; + private int missedCallNum = 1; public SimulatorMainPortal(AppCompatActivity activity) { this.activity = activity; @@ -38,8 +44,50 @@ public final class SimulatorMainPortal { buildMainPortal(); } + public SimulatorMainPortal(Context context) { + this.activity = null; + this.context = context; + buildMainPortal(); + } + + public void setCallerId(String callerId) { + this.callerId = callerId; + } + + public void setPresentation(int presentation) { + this.presentation = presentation; + } + + public void setMissedCallNum(int missedCallNum) { + this.missedCallNum = missedCallNum; + } + + /** + * Executes commands sent to this portal. + * + * @param commands a string array that stores commands that trigger runnable methods stored in the + * portal. For example: ["VoiceCall", "Incoming Call"] triggers "() -> new + * SimulatorVoiceCall(context).addNewIncomingCall()" runnable method. + */ + public void execute(String[] commands) { + @SuppressWarnings("unused") + ListenableFuture executeCommand = + DialerExecutorComponent.get(context) + .backgroundExecutor() + .submit(() -> execute(simulatorPortalEntryGroup, commands, 0)); + } + + private void execute( + SimulatorPortalEntryGroup simulatorPortalEntryGroup, String[] commands, int index) { + if (simulatorPortalEntryGroup.methods().containsKey(commands[index])) { + simulatorPortalEntryGroup.methods().get(commands[index]).run(); + } else if (simulatorPortalEntryGroup.subPortals().containsKey(commands[index])) { + execute(simulatorPortalEntryGroup.subPortals().get(commands[index]), commands, index + 1); + } + } + private void buildMainPortal() { - this.simulatorMainPortal = + simulatorPortalEntryGroup = SimulatorPortalEntryGroup.builder() .setMethods( ImmutableMap.builder() @@ -91,12 +139,26 @@ public final class SimulatorMainPortal { ImmutableMap.builder() .put("Incoming call", () -> new SimulatorVoiceCall(context).addNewIncomingCall()) .put("Outgoing call", () -> new SimulatorVoiceCall(context).addNewOutgoingCall()) + .put( + "Customized incoming call (Dialog)", + () -> + new SimulatorVoiceCall(context) + .addCustomizedIncomingCallWithDialog(activity)) + .put( + "Customized outgoing call (Dialog)", + () -> + new SimulatorVoiceCall(context) + .addCustomizedOutgoingCallWithDialog(activity)) .put( "Customized incoming call", - () -> new SimulatorVoiceCall(context).addNewIncomingCall(activity)) + () -> + new SimulatorVoiceCall(context) + .addCustomizedIncomingCall(this.callerId, this.presentation)) .put( "Customized outgoing call", - () -> new SimulatorVoiceCall(context).addNewOutgoingCall(activity)) + () -> + new SimulatorVoiceCall(context) + .addCustomizedOutgoingCall(this.callerId, this.presentation)) .put( "Incoming enriched call", () -> new SimulatorVoiceCall(context).incomingEnrichedCall()) @@ -173,9 +235,7 @@ public final class SimulatorMainPortal { .start(SimulatorUtils.NOTIFICATION_COUNT)) .put( "Missed calls (few)", - () -> - new SimulatorMissedCallCreator(context) - .start(SimulatorUtils.NOTIFICATION_COUNT_FEW)) + () -> new SimulatorMissedCallCreator(context).start(missedCallNum)) .put( "Voicemails", () -> @@ -186,6 +246,6 @@ public final class SimulatorMainPortal { } public ActionProvider getActionProvider() { - return new SimulatorMenu(context, simulatorMainPortal); + return new SimulatorMenu(context, simulatorPortalEntryGroup); } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index 4b3033f32..9e38470ac 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -92,7 +92,15 @@ final class SimulatorVoiceCall context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE); } - void addNewIncomingCall(AppCompatActivity activity) { + void addCustomizedIncomingCall(String callerId, int callerIdPresentation) { + Bundle extras = new Bundle(); + extras.putInt(Simulator.PRESENTATION_CHOICE, callerIdPresentation); + connectionTag = + SimulatorSimCallManager.addNewIncomingCall( + context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE, extras); + } + + void addCustomizedIncomingCallWithDialog(AppCompatActivity activity) { SimulatorDialogFragment.newInstance( (callerId, callerIdPresentation) -> { Bundle extras = new Bundle(); @@ -111,7 +119,15 @@ final class SimulatorVoiceCall context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE); } - void addNewOutgoingCall(AppCompatActivity activity) { + void addCustomizedOutgoingCall(String callerId, int callerIdPresentation) { + Bundle extras = new Bundle(); + extras.putInt(Simulator.PRESENTATION_CHOICE, callerIdPresentation); + connectionTag = + SimulatorSimCallManager.addNewIncomingCall( + context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE, extras); + } + + void addCustomizedOutgoingCallWithDialog(AppCompatActivity activity) { SimulatorDialogFragment.newInstance( (callerId, callerIdPresentation) -> { Bundle extras = new Bundle(); diff --git a/java/com/android/dialer/simulator/service/AndroidManifest.xml b/java/com/android/dialer/simulator/service/AndroidManifest.xml new file mode 100644 index 000000000..37f26bad3 --- /dev/null +++ b/java/com/android/dialer/simulator/service/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/simulator/service/ISimulatorService.aidl b/java/com/android/dialer/simulator/service/ISimulatorService.aidl new file mode 100644 index 000000000..77511cc9c --- /dev/null +++ b/java/com/android/dialer/simulator/service/ISimulatorService.aidl @@ -0,0 +1,45 @@ +package com.android.dialer.simulator.service; + +interface ISimulatorService { + /** + * Makes an incoming call by simulator api. + * @param callerId is the number showing on incall UI. + * @param presentation is one of types of a call e.g. Payphone, Restricted, etc.. check + * {@link TelecomManager} for more information. + * */ + void makeIncomingCall(String callerId, int presentation); + /** + * Makes an incoming call. + * @param callerId the number showing on incall UI. + * @param presentation one of types of a call e.g. Payphone, Restricted, etc.. check + * {@link TelecomManager} for more information. + * */ + void makeOutgoingCall(String callerId, int presentation); + /** + * Makes an incoming enriched call. + * Note: simulator mode should be enabled first. + * */ + void makeIncomingEnrichedCall(); + /** + * Makes an outgoing enriched call. + * Note: simulator mode should be enabled first. + * */ + void makeOutgoingEnrichedCall(); + /** + * Populate missed call logs. + * @param num the number of missed call to make with this api. + * */ + void populateMissedCall(int num); + /** Populate contacts database to get contacts, call logs, voicemails, etc.. */ + void populateDataBase(); + /** Clean contacts database to clean all exsting contacts, call logs. voicemails, etc.. */ + void cleanDataBase(); + /** + * Enable simulator mode. After entering simulator mode, all calls made by dialer will be handled + * by simulator connection service, meaning users can directly make fake calls through simulator. + * It is also a prerequisite to make an enriched call. + * */ + void enableSimulatorMode(); + /** Disable simulator mode to use system connection service. */ + void disableSimulatorMode(); +} \ No newline at end of file diff --git a/java/com/android/dialer/simulator/service/SimulatorService.java b/java/com/android/dialer/simulator/service/SimulatorService.java new file mode 100644 index 000000000..9b51b1b7f --- /dev/null +++ b/java/com/android/dialer/simulator/service/SimulatorService.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2018 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.dialer.simulator.service; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.support.annotation.Nullable; +import com.android.dialer.simulator.impl.SimulatorMainPortal; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * A secured android service that gives clients simulator api access through binder if clients do + * have registered certificates. + */ +public class SimulatorService extends Service { + + private static final String POPULATE_DATABASE = "Populate database"; + private static final String CLEAN_DATABASE = "Clean database"; + private static final String ENABLE_SIMULATOR_MODE = "Enable simulator mode"; + private static final String DISABLE_SIMULATOR_MODE = "Disable simulator mode"; + private static final String VOICECALL = "VoiceCall"; + private static final String NOTIFICATIONS = "Notifications"; + private static final String CUSTOMIZED_INCOMING_CALL = "Customized incoming call"; + private static final String CUSTOMIZED_OUTGOING_CALL = "Customized outgoing call"; + private static final String INCOMING_ENRICHED_CALL = "Incoming enriched call"; + private static final String OUTGOING_ENRICHED_CALL = "Outgoing enriched call"; + private static final String MISSED_CALL = "Missed calls (few)"; + + // Certificates that used for checking whether a client is a trusted client. + // To get a hashed certificate + private ImmutableList certificates; + + private SimulatorMainPortal simulatorMainPortal; + + /** + * The implementation of {@link ISimulatorService} that contains logic for clients to call + * simulator api. + */ + private final ISimulatorService.Stub binder = + new ISimulatorService.Stub() { + + @Override + public void makeIncomingCall(String callerId, int presentation) { + doSecurityCheck( + () -> { + simulatorMainPortal.setCallerId(callerId); + simulatorMainPortal.setPresentation(presentation); + simulatorMainPortal.execute(new String[] {VOICECALL, CUSTOMIZED_INCOMING_CALL}); + }); + } + + @Override + public void makeOutgoingCall(String callerId, int presentation) { + doSecurityCheck( + () -> { + simulatorMainPortal.setCallerId(callerId); + simulatorMainPortal.setPresentation(presentation); + simulatorMainPortal.execute(new String[] {VOICECALL, CUSTOMIZED_OUTGOING_CALL}); + }); + } + + @Override + public void populateDataBase() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {POPULATE_DATABASE}); + }); + } + + @Override + public void cleanDataBase() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {CLEAN_DATABASE}); + }); + } + + @Override + public void enableSimulatorMode() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {ENABLE_SIMULATOR_MODE}); + }); + } + + @Override + public void disableSimulatorMode() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {DISABLE_SIMULATOR_MODE}); + }); + } + + @Override + public void makeIncomingEnrichedCall() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {VOICECALL, INCOMING_ENRICHED_CALL}); + }); + } + + @Override + public void makeOutgoingEnrichedCall() throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.execute(new String[] {VOICECALL, OUTGOING_ENRICHED_CALL}); + }); + } + + @Override + public void populateMissedCall(int num) throws RemoteException { + doSecurityCheck( + () -> { + simulatorMainPortal.setMissedCallNum(num); + simulatorMainPortal.execute(new String[] {NOTIFICATIONS, MISSED_CALL}); + }); + } + + private void doSecurityCheck(Runnable runnable) { + if (!hasAccessToService()) { + throw new RuntimeException("Client doesn't have access to Simulator service!"); + } + runnable.run(); + } + }; + + /** Sets SimulatorMainPortal instance for SimulatorService. */ + public void setSimulatorMainPortal(SimulatorMainPortal simulatorMainPortal) { + this.simulatorMainPortal = simulatorMainPortal; + } + + /** Sets immutable CertificatesList for SimulatorService. */ + public void setCertificatesList(ImmutableList certificates) { + this.certificates = certificates; + } + + private boolean hasAccessToService() { + int clientPid = Binder.getCallingPid(); + if (clientPid == Process.myPid()) { + throw new RuntimeException("Client and service have the same PID!"); + } + Optional packageName = getPackageNameForPid(clientPid); + if (packageName.isPresent()) { + try { + PackageInfo packageInfo = + getPackageManager().getPackageInfo(packageName.get(), PackageManager.GET_SIGNATURES); + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + if (packageInfo.signatures.length != 1) { + throw new NotOnlyOneSignatureException("The client has more than one signature!"); + } + Signature signature = packageInfo.signatures[0]; + return isCertificateValid(messageDigest.digest(signature.toByteArray()), this.certificates); + } catch (NameNotFoundException | NoSuchAlgorithmException | NotOnlyOneSignatureException e) { + throw new RuntimeException(e); + } + } + return false; + } + + private Optional getPackageNameForPid(int pid) { + ActivityManager activityManager = + (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); + for (RunningAppProcessInfo processInfo : activityManager.getRunningAppProcesses()) { + if (processInfo.pid == pid) { + return Optional.of(processInfo.processName); + } + } + return Optional.absent(); + } + + private static boolean isCertificateValid( + byte[] clientCerfificate, ImmutableList certificates) { + for (String certificate : certificates) { + if (certificate.equals(bytesToHexString(clientCerfificate))) { + return true; + } + } + return false; + } + + private static String bytesToHexString(byte[] in) { + final StringBuilder builder = new StringBuilder(); + for (byte b : in) { + builder.append(String.format("%02X", b)); + } + return builder.toString(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + private static class NotOnlyOneSignatureException extends Exception { + public NotOnlyOneSignatureException(String desc) { + super(desc); + } + } +} diff --git a/java/com/android/dialer/simulator/service/SimulatorServiceClient.java b/java/com/android/dialer/simulator/service/SimulatorServiceClient.java new file mode 100644 index 000000000..7917dfb66 --- /dev/null +++ b/java/com/android/dialer/simulator/service/SimulatorServiceClient.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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.dialer.simulator.service; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; + +/** Contains the basic logic that a simulator service client needs to get access to the service. */ +public abstract class SimulatorServiceClient { + + /** Initiates service connection. */ + public void connectionService(Context context) { + Intent intent = new Intent(context, SimulatorService.class); + SimulatorServiceConnection mConnection = new SimulatorServiceConnection(); + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + mConnection.bindToClient(this); + } + + /** Contains client logic using SimulatorService api defined in ISimulatorService.aidl. */ + public abstract void process(ISimulatorService service) throws RemoteException; + + private void onServiceConnected(ISimulatorService service) { + try { + process(service); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private void onServiceDisconnected() {} + + static class SimulatorServiceConnection implements ServiceConnection { + + private SimulatorServiceClient client; + private ISimulatorService simulatorService; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + simulatorService = ISimulatorService.Stub.asInterface(service); + client.onServiceConnected(simulatorService); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + client.onServiceDisconnected(); + } + + void bindToClient(SimulatorServiceClient client) { + this.client = client; + } + } +} -- cgit v1.2.3