summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/simulator
diff options
context:
space:
mode:
authoryueg <yueg@google.com>2017-09-12 11:10:45 -0700
committerEric Erfanian <erfanian@google.com>2017-09-13 14:15:01 -0700
commit7f78e9a692d7d7ca1f1204421adce91545a880f8 (patch)
tree955bc0586790d75ec67753586e905eccf4224d98 /java/com/android/dialer/simulator
parentb21b9f9b2efbc358ee9806af87b7edc3f82af4da (diff)
Log swiping and clicking for switching tabs.
If it's a swipe, onPageScrolled() is called several times before onPageScrollStateChanged(SCROLL_STATE_SETTLING) and onPageSelected(). If it's a click, only onPageScrollStateChanged(SCROLL_STATE_SETTLING) is called before onPageSelected(). And onPageScrollStateChanged(SCROLL_STATE_SETTLING) will not be called if user don't switch to a new tab. We use the difference to tell if user switching tabs by swiping or clicking. Test: DialtactsActivityTest PiperOrigin-RevId: 168403148 Change-Id: Iaaf84ab9c4955d0bc2c1e9857ba59fd37b3984af
Diffstat (limited to 'java/com/android/dialer/simulator')
-rw-r--r--java/com/android/dialer/simulator/Simulator.java22
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorActionProvider.java165
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConnection.java39
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConnectionService.java120
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorImpl.java2
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorMainMenu.java113
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java2
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorNotifications.java78
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java166
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java163
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java199
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java2
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorSubMenu.java100
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorVideoCall.java164
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java125
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java103
16 files changed, 1207 insertions, 356 deletions
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java
index f4164158e..f753e5f6b 100644
--- a/java/com/android/dialer/simulator/Simulator.java
+++ b/java/com/android/dialer/simulator/Simulator.java
@@ -22,6 +22,7 @@ import android.support.annotation.Nullable;
import android.view.ActionProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/** Used to add menu items to the Dialer menu to test the app using simulated calls and data. */
public interface Simulator {
@@ -42,6 +43,7 @@ public interface Simulator {
DISCONNECT,
STATE_CHANGE,
DTMF,
+ SESSION_MODIFY_REQUEST,
})
public @interface Type {}
@@ -53,6 +55,7 @@ public interface Simulator {
public static final int DISCONNECT = 5;
public static final int STATE_CHANGE = 6;
public static final int DTMF = 7;
+ public static final int SESSION_MODIFY_REQUEST = 8;
@Type public final int type;
/** Holds event specific information. For example, for DTMF this could be the keycode. */
@@ -71,5 +74,24 @@ public interface Simulator {
this.data1 = data1;
this.data2 = data2;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Event)) {
+ return false;
+ }
+ Event event = (Event) other;
+ return type == event.type
+ && Objects.equals(data1, event.data1)
+ && Objects.equals(data2, event.data2);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Integer.valueOf(type), data1, data2);
+ }
}
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java b/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java
deleted file mode 100644
index f095a5993..000000000
--- a/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2017 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.impl;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.provider.VoicemailContract;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.view.ActionProvider;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutor.Worker;
-import com.android.dialer.common.concurrent.DialerExecutors;
-import com.android.dialer.databasepopulator.CallLogPopulator;
-import com.android.dialer.databasepopulator.ContactsPopulator;
-import com.android.dialer.databasepopulator.VoicemailPopulator;
-import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity;
-import com.android.dialer.persistentlog.PersistentLogger;
-
-/** Implements the simulator submenu. */
-final class SimulatorActionProvider extends ActionProvider {
- @NonNull private final Context context;
-
- private static class ShareLogWorker implements Worker<Void, String> {
-
- @Nullable
- @Override
- public String doInBackground(Void unused) {
- return PersistentLogger.dumpLogToString();
- }
- }
-
- public SimulatorActionProvider(@NonNull Context context) {
- super(Assert.isNotNull(context));
- this.context = context;
- }
-
- @Override
- public View onCreateActionView() {
- LogUtil.enterBlock("SimulatorActionProvider.onCreateActionView(null)");
- return null;
- }
-
- @Override
- public View onCreateActionView(MenuItem forItem) {
- LogUtil.enterBlock("SimulatorActionProvider.onCreateActionView(MenuItem)");
- return null;
- }
-
- @Override
- public boolean hasSubMenu() {
- LogUtil.enterBlock("SimulatorActionProvider.hasSubMenu");
- return true;
- }
-
- @Override
- public void onPrepareSubMenu(SubMenu subMenu) {
- super.onPrepareSubMenu(subMenu);
- LogUtil.enterBlock("SimulatorActionProvider.onPrepareSubMenu");
- subMenu.clear();
-
- subMenu
- .add("Add call")
- .setOnMenuItemClickListener(
- (item) -> {
- SimulatorVoiceCall.addNewIncomingCall(context);
- return true;
- });
-
- subMenu
- .add("Notifiations")
- .setActionProvider(SimulatorNotifications.getActionProvider(context));
- subMenu
- .add("Populate database")
- .setOnMenuItemClickListener(
- (item) -> {
- populateDatabase();
- return true;
- });
- subMenu
- .add("Clean database")
- .setOnMenuItemClickListener(
- (item) -> {
- cleanDatabase();
- return true;
- });
- subMenu
- .add("Sync Voicemail")
- .setOnMenuItemClickListener(
- (item) -> {
- Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
- context.sendBroadcast(intent);
- return true;
- });
-
- subMenu
- .add("Share persistent log")
- .setOnMenuItemClickListener(
- (item) -> {
- DialerExecutors.createNonUiTaskBuilder(new ShareLogWorker())
- .onSuccess(
- (String log) -> {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_TEXT, log);
- if (intent.resolveActivity(context.getPackageManager()) != null) {
- context.startActivity(intent);
- }
- })
- .build()
- .executeSerial(null);
- return true;
- });
- subMenu
- .add("Enriched call simulator")
- .setOnMenuItemClickListener(
- (item) -> {
- context.startActivity(EnrichedCallSimulatorActivity.newIntent(context));
- return true;
- });
- }
-
- private void populateDatabase() {
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- ContactsPopulator.populateContacts(context);
- CallLogPopulator.populateCallLog(context);
- VoicemailPopulator.populateVoicemail(context);
- return null;
- }
- }.execute();
- }
-
- private void cleanDatabase() {
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- ContactsPopulator.deleteAllContacts(context);
- CallLogPopulator.deleteAllCallLog(context);
- VoicemailPopulator.deleteAllVoicemail(context);
- return null;
- }
- }.execute();
- }
-}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index b462b5405..70c1095dc 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -16,8 +16,11 @@
package com.android.dialer.simulator.impl;
+import android.content.Context;
import android.support.annotation.NonNull;
import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.VideoProfile;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.simulator.Simulator.Event;
@@ -30,6 +33,18 @@ public final class SimulatorConnection extends Connection {
private final List<Event> events = new ArrayList<>();
private int currentState = STATE_NEW;
+ SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) {
+ Assert.isNotNull(context);
+ Assert.isNotNull(request);
+ putExtras(request.getExtras());
+ setConnectionCapabilities(
+ CAPABILITY_MUTE
+ | CAPABILITY_SUPPORT_HOLD
+ | CAPABILITY_HOLD
+ | CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+ setVideoProvider(new SimulatorVideoProvider(context, this));
+ }
+
public void addListener(@NonNull Listener listener) {
listeners.add(Assert.isNotNull(listener));
}
@@ -44,9 +59,9 @@ public final class SimulatorConnection extends Connection {
}
@Override
- public void onAnswer() {
+ public void onAnswer(int videoState) {
LogUtil.enterBlock("SimulatorConnection.onAnswer");
- onEvent(new Event(Event.ANSWER));
+ onEvent(new Event(Event.ANSWER, Integer.toString(videoState), null));
}
@Override
@@ -75,9 +90,14 @@ public final class SimulatorConnection extends Connection {
@Override
public void onStateChanged(int newState) {
- LogUtil.enterBlock("SimulatorConnection.onStateChanged");
- onEvent(new Event(Event.STATE_CHANGE, stateToString(currentState), stateToString(newState)));
+ LogUtil.i(
+ "SimulatorConnection.onStateChanged",
+ "%s -> %s",
+ stateToString(currentState),
+ stateToString(newState));
+ int oldState = currentState;
currentState = newState;
+ onEvent(new Event(Event.STATE_CHANGE, stateToString(oldState), stateToString(newState)));
}
@Override
@@ -86,13 +106,22 @@ public final class SimulatorConnection extends Connection {
onEvent(new Event(Event.DTMF, Character.toString(c), null));
}
- private void onEvent(@NonNull Event event) {
+ void onEvent(@NonNull Event event) {
events.add(Assert.isNotNull(event));
for (Listener listener : listeners) {
listener.onEvent(this, event);
}
}
+ void handleSessionModifyRequest(@NonNull Event event) {
+ VideoProfile fromProfile = new VideoProfile(Integer.parseInt(event.data1));
+ VideoProfile toProfile = new VideoProfile(Integer.parseInt(event.data2));
+ setVideoState(toProfile.getVideoState());
+ getVideoProvider()
+ .receiveSessionModifyResponse(
+ Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS, fromProfile, toProfile);
+ }
+
/** Callback for when a new event arrives. */
public interface Listener {
void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event);
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
index 06c2591cc..25d4a7240 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
@@ -16,10 +16,7 @@
package com.android.dialer.simulator.impl;
-import android.content.ComponentName;
-import android.content.Context;
import android.net.Uri;
-import android.os.Bundle;
import android.support.annotation.NonNull;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -34,72 +31,15 @@ import com.android.dialer.common.LogUtil;
import java.util.ArrayList;
import java.util.List;
-/** Simple connection provider to create an incoming call. This is useful for emulators. */
+/** Simple connection provider to create phone calls. This is useful for emulators. */
public class SimulatorConnectionService extends ConnectionService {
-
- private static final String PHONE_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
- private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
private static final List<Listener> listeners = new ArrayList<>();
private static SimulatorConnectionService instance;
- private static void register(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorConnectionService.register");
- Assert.isNotNull(context);
- context.getSystemService(TelecomManager.class).registerPhoneAccount(buildPhoneAccount(context));
- }
-
- private static void unregister(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorConnectionService.unregister");
- Assert.isNotNull(context);
- context
- .getSystemService(TelecomManager.class)
- .unregisterPhoneAccount(buildPhoneAccount(context).getAccountHandle());
- }
-
public static SimulatorConnectionService getInstance() {
return instance;
}
- public static void addNewOutgoingCall(
- @NonNull Context context, @NonNull Bundle extras, @NonNull String phoneNumber) {
- LogUtil.enterBlock("SimulatorConnectionService.addNewOutgoingCall");
- Assert.isNotNull(context);
- Assert.isNotNull(extras);
- Assert.isNotNull(phoneNumber);
-
- register(context);
-
- Bundle bundle = new Bundle(extras);
- bundle.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
- Bundle outgoingCallExtras = new Bundle();
- outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, bundle);
-
- // Use the system's phone account so that these look like regular SIM call.
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- telecomManager.placeCall(
- Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras);
- }
-
- public static void addNewIncomingCall(
- @NonNull Context context, @NonNull Bundle extras, @NonNull String callerId) {
- LogUtil.enterBlock("SimulatorConnectionService.addNewIncomingCall");
- Assert.isNotNull(context);
- Assert.isNotNull(extras);
- Assert.isNotNull(callerId);
-
- register(context);
-
- Bundle bundle = new Bundle(extras);
- bundle.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId);
- bundle.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
-
- // Use the system's phone account so that these look like regular SIM call.
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- PhoneAccountHandle systemPhoneAccount =
- telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
- telecomManager.addNewIncomingCall(systemPhoneAccount, bundle);
- }
-
public static void addListener(@NonNull Listener listener) {
listeners.add(Assert.isNotNull(listener));
}
@@ -108,32 +48,6 @@ public class SimulatorConnectionService extends ConnectionService {
listeners.remove(Assert.isNotNull(listener));
}
- @NonNull
- private static PhoneAccount buildPhoneAccount(Context context) {
- PhoneAccount.Builder builder =
- new PhoneAccount.Builder(
- getConnectionServiceHandle(context), "Simulator connection service");
- List<String> uriSchemes = new ArrayList<>();
- uriSchemes.add(PhoneAccount.SCHEME_TEL);
-
- return builder
- .setCapabilities(
- PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
- .setShortDescription("Simulator Connection Service")
- .setSupportedUriSchemes(uriSchemes)
- .build();
- }
-
- public static PhoneAccountHandle getConnectionServiceHandle(Context context) {
- return new PhoneAccountHandle(
- new ComponentName(context, SimulatorConnectionService.class), PHONE_ACCOUNT_ID);
- }
-
- private static Uri getPhoneNumber(ConnectionRequest request) {
- String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
- return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
- }
-
@Override
public void onCreate() {
super.onCreate();
@@ -151,25 +65,19 @@ public class SimulatorConnectionService extends ConnectionService {
public Connection onCreateOutgoingConnection(
PhoneAccountHandle phoneAccount, ConnectionRequest request) {
LogUtil.enterBlock("SimulatorConnectionService.onCreateOutgoingConnection");
- if (!isSimulatorConnectionRequest(request)) {
+ if (!SimulatorSimCallManager.isSimulatorConnectionRequest(request)) {
LogUtil.i(
"SimulatorConnectionService.onCreateOutgoingConnection",
"outgoing call not from simulator, unregistering");
- Toast.makeText(
- this, "Unregistering Dialer simulator, making a real phone call", Toast.LENGTH_LONG)
+ Toast.makeText(this, "Unregistering simulator, making a real phone call", Toast.LENGTH_LONG)
.show();
- unregister(this);
+ SimulatorSimCallManager.unregister(this);
return null;
}
- SimulatorConnection connection = new SimulatorConnection();
+ SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setDialing();
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
- connection.setConnectionCapabilities(
- Connection.CAPABILITY_MUTE
- | Connection.CAPABILITY_SUPPORT_HOLD
- | Connection.CAPABILITY_HOLD);
- connection.putExtras(request.getExtras());
for (Listener listener : listeners) {
listener.onNewOutgoingConnection(connection);
@@ -181,23 +89,19 @@ public class SimulatorConnectionService extends ConnectionService {
public Connection onCreateIncomingConnection(
PhoneAccountHandle phoneAccount, ConnectionRequest request) {
LogUtil.enterBlock("SimulatorConnectionService.onCreateIncomingConnection");
- if (!isSimulatorConnectionRequest(request)) {
+ if (!SimulatorSimCallManager.isSimulatorConnectionRequest(request)) {
LogUtil.i(
"SimulatorConnectionService.onCreateIncomingConnection",
"incoming call not from simulator, unregistering");
- Toast.makeText(
- this, "Unregistering Dialer simulator, got a real incoming call", Toast.LENGTH_LONG)
+ Toast.makeText(this, "Unregistering simulator, got a real incoming call", Toast.LENGTH_LONG)
.show();
- unregister(this);
+ SimulatorSimCallManager.unregister(this);
return null;
}
- SimulatorConnection connection = new SimulatorConnection();
+ SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setRinging();
connection.setAddress(getPhoneNumber(request), TelecomManager.PRESENTATION_ALLOWED);
- connection.setConnectionCapabilities(
- Connection.CAPABILITY_MUTE | Connection.CAPABILITY_SUPPORT_HOLD);
- connection.putExtras(request.getExtras());
for (Listener listener : listeners) {
listener.onNewIncomingConnection(connection);
@@ -205,9 +109,9 @@ public class SimulatorConnectionService extends ConnectionService {
return connection;
}
- private static boolean isSimulatorConnectionRequest(@NonNull ConnectionRequest request) {
- return request.getExtras() != null
- && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION);
+ private static Uri getPhoneNumber(ConnectionRequest request) {
+ String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
+ return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
}
/** Callback used to notify listeners when a new connection has been added. */
diff --git a/java/com/android/dialer/simulator/impl/SimulatorImpl.java b/java/com/android/dialer/simulator/impl/SimulatorImpl.java
index 2dd180ef4..d6ee5ef38 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorImpl.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorImpl.java
@@ -35,6 +35,6 @@ final class SimulatorImpl implements Simulator {
@Override
public ActionProvider getActionProvider(Context context) {
- return new SimulatorActionProvider(context);
+ return SimulatorMainMenu.getActionProvider(context);
}
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
new file mode 100644
index 000000000..d663d584d
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.VoicemailContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.ActionProvider;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.databasepopulator.CallLogPopulator;
+import com.android.dialer.databasepopulator.ContactsPopulator;
+import com.android.dialer.databasepopulator.VoicemailPopulator;
+import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity;
+import com.android.dialer.persistentlog.PersistentLogger;
+
+/** Implements the top level simulator menu. */
+final class SimulatorMainMenu {
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem("Voice call", SimulatorVoiceCall.getActionProvider(context))
+ .addItem("IMS video", SimulatorVideoCall.getActionProvider(context))
+ .addItem("Notifications", SimulatorNotifications.getActionProvider(context))
+ .addItem("Populate database", () -> populateDatabase(context))
+ .addItem("Clean database", () -> cleanDatabase(context))
+ .addItem("Sync voicemail", () -> syncVoicemail(context))
+ .addItem("Share persistent log", () -> sharePersistentLog(context))
+ .addItem(
+ "Enriched call simulator",
+ () -> context.startActivity(EnrichedCallSimulatorActivity.newIntent(context)));
+ }
+
+ private static void populateDatabase(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new PopulateDatabaseWorker())
+ .build()
+ .executeSerial(context);
+ }
+
+ private static void cleanDatabase(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new CleanDatabaseWorker())
+ .build()
+ .executeSerial(context);
+ }
+
+ private static void syncVoicemail(@NonNull Context context) {
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ context.sendBroadcast(intent);
+ }
+
+ private static void sharePersistentLog(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new ShareLogWorker())
+ .onSuccess(
+ (String log) -> {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, log);
+ if (intent.resolveActivity(context.getPackageManager()) != null) {
+ context.startActivity(intent);
+ }
+ })
+ .build()
+ .executeSerial(null);
+ }
+
+ private SimulatorMainMenu() {}
+
+ private static class PopulateDatabaseWorker implements Worker<Context, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(Context context) {
+ ContactsPopulator.populateContacts(context);
+ CallLogPopulator.populateCallLog(context);
+ VoicemailPopulator.populateVoicemail(context);
+ return null;
+ }
+ }
+
+ private static class CleanDatabaseWorker implements Worker<Context, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(Context context) {
+ ContactsPopulator.deleteAllContacts(context);
+ CallLogPopulator.deleteAllCallLog(context);
+ VoicemailPopulator.deleteAllVoicemail(context);
+ return null;
+ }
+ }
+
+ private static class ShareLogWorker implements Worker<Void, String> {
+ @Nullable
+ @Override
+ public String doInBackground(Void unused) {
+ return PersistentLogger.dumpLogToString();
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
index 22eb96731..f85f46602 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
@@ -74,7 +74,7 @@ final class SimulatorMissedCallCreator implements SimulatorConnectionService.Lis
extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
extras.putBoolean(EXTRA_IS_MISSED_CALL_CONNECTION, true);
- SimulatorConnectionService.addNewIncomingCall(context, extras, callerId);
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras);
}
private static boolean isMissedCallConnection(@NonNull Connection connection) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorNotifications.java b/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
index ebe8ecd46..3f402d317 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
@@ -20,10 +20,6 @@ import android.content.Context;
import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.NonNull;
import android.view.ActionProvider;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.databasepopulator.VoicemailPopulator;
import java.util.concurrent.TimeUnit;
@@ -33,68 +29,18 @@ final class SimulatorNotifications {
private static final int NOTIFICATION_COUNT = 12;
static ActionProvider getActionProvider(@NonNull Context context) {
- return new NotificationsActionProvider(context);
- }
-
- private static class NotificationsActionProvider extends ActionProvider {
- @NonNull private final Context context;
-
- public NotificationsActionProvider(@NonNull Context context) {
- super(Assert.isNotNull(context));
- this.context = context;
- }
-
- @Override
- public View onCreateActionView() {
- return null;
- }
-
- @Override
- public View onCreateActionView(MenuItem forItem) {
- return null;
- }
-
- @Override
- public boolean hasSubMenu() {
- return true;
- }
-
- @Override
- public void onPrepareSubMenu(@NonNull SubMenu subMenu) {
- LogUtil.enterBlock("NotificationsActionProvider.onPrepareSubMenu");
- Assert.isNotNull(subMenu);
- super.onPrepareSubMenu(subMenu);
-
- subMenu.clear();
- subMenu
- .add("Missed Calls")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorMissedCallCreator(context).start(NOTIFICATION_COUNT);
- return true;
- });
- subMenu
- .add("Voicemails")
- .setOnMenuItemClickListener(
- (item) -> {
- addVoicemailNotifications(context);
- return true;
- });
- subMenu
- .add("Non spam")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorSpamCallCreator(context, false /* isSpam */).start(NOTIFICATION_COUNT);
- return true;
- });
- subMenu
- .add("Confirm spam")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorSpamCallCreator(context, true /* isSpam */).start(NOTIFICATION_COUNT);
- return true;
- });
- }
+ return new SimulatorSubMenu(context)
+ .addItem(
+ "Missed calls", () -> new SimulatorMissedCallCreator(context).start(NOTIFICATION_COUNT))
+ .addItem("Voicemails", () -> addVoicemailNotifications(context))
+ .addItem(
+ "Non spam",
+ () ->
+ new SimulatorSpamCallCreator(context, false /* isSpam */).start(NOTIFICATION_COUNT))
+ .addItem(
+ "Confirm spam",
+ () ->
+ new SimulatorSpamCallCreator(context, true /* isSpam */).start(NOTIFICATION_COUNT));
}
private static void addVoicemailNotifications(@NonNull Context context) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java b/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java
new file mode 100644
index 000000000..e089f75ff
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.util.Size;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import java.util.Arrays;
+
+/**
+ * Used by the video provider to draw the local camera. The in-call UI is responsible for setting
+ * the camera (front or back) and the view to draw to. The video provider then uses this class to
+ * capture frames from the given camera and draw to the given view.
+ */
+final class SimulatorPreviewCamera {
+ @NonNull private final Context context;
+ @NonNull private final String cameraId;
+ @NonNull private final Surface surface;
+ @Nullable private CameraDevice camera;
+ private boolean isStopped;
+
+ SimulatorPreviewCamera(
+ @NonNull Context context, @NonNull String cameraId, @NonNull Surface surface) {
+ this.context = Assert.isNotNull(context);
+ this.cameraId = Assert.isNotNull(cameraId);
+ this.surface = Assert.isNotNull(surface);
+ }
+
+ void startCamera() {
+ LogUtil.enterBlock("SimulatorPreviewCamera.startCamera");
+ Assert.checkState(!isStopped);
+ try {
+ context
+ .getSystemService(CameraManager.class)
+ .openCamera(cameraId, new CameraListener(), null /* handler */);
+ } catch (CameraAccessException | SecurityException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ void stopCamera() {
+ LogUtil.enterBlock("SimulatorPreviewCamera.stopCamera");
+ isStopped = true;
+ if (camera != null) {
+ camera.close();
+ camera = null;
+ }
+ }
+
+ @Nullable
+ static CameraCapabilities getCameraCapabilities(
+ @NonNull Context context, @Nullable String cameraId) {
+ if (cameraId == null) {
+ LogUtil.e("SimulatorPreviewCamera.getCameraCapabilities", "null camera ID");
+ return null;
+ }
+
+ CameraManager cameraManager = context.getSystemService(CameraManager.class);
+ CameraCharacteristics characteristics;
+ try {
+ characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+
+ StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size previewSize = map.getOutputSizes(SurfaceTexture.class)[0];
+ LogUtil.i("SimulatorPreviewCamera.getCameraCapabilities", "preview size: " + previewSize);
+ return new CameraCapabilities(previewSize.getWidth(), previewSize.getHeight());
+ }
+
+ private final class CameraListener extends CameraDevice.StateCallback {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onOpened");
+ SimulatorPreviewCamera.this.camera = camera;
+ if (isStopped) {
+ LogUtil.i("SimulatorPreviewCamera.CameraListener.onOpened", "stopped");
+ stopCamera();
+ return;
+ }
+
+ try {
+ camera.createCaptureSession(
+ Arrays.asList(Assert.isNotNull(surface)),
+ new CaptureSessionCallback(),
+ null /* handler */);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ LogUtil.i("SimulatorPreviewCamera.CameraListener.onError", "error: " + error);
+ stopCamera();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onDisconnected");
+ stopCamera();
+ }
+
+ @Override
+ public void onClosed(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onCLosed");
+ }
+ }
+
+ private final class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession session) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CaptureSessionCallback.onConfigured");
+
+ if (isStopped) {
+ LogUtil.i("SimulatorPreviewCamera.CaptureSessionCallback.onConfigured", "stopped");
+ stopCamera();
+ return;
+ }
+ try {
+ CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ builder.addTarget(surface);
+ builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ session.setRepeatingRequest(
+ builder.build(), null /* captureCallback */, null /* handler */);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CaptureSessionCallback.onConfigureFailed");
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java b/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java
new file mode 100644
index 000000000..b14bba39a
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+
+/**
+ * Used by the video provider to draw the remote party's video. The in-call UI is responsible for
+ * setting the view to draw to. Since the simulator doesn't have a remote party we simply draw a
+ * green screen with a ball bouncing around.
+ */
+final class SimulatorRemoteVideo {
+ @NonNull private final RenderThread thread;
+ private boolean isStopped;
+
+ SimulatorRemoteVideo(@NonNull Surface surface) {
+ thread = new RenderThread(new Renderer(surface));
+ }
+
+ void startVideo() {
+ LogUtil.enterBlock("SimulatorRemoteVideo.startVideo");
+ Assert.checkState(!isStopped);
+ thread.start();
+ }
+
+ void stopVideo() {
+ LogUtil.enterBlock("SimulatorRemoteVideo.stopVideo");
+ isStopped = true;
+ thread.quitSafely();
+ }
+
+ @VisibleForTesting
+ Runnable getRenderer() {
+ return thread.getRenderer();
+ }
+
+ private static class Renderer implements Runnable {
+ private static final int FRAME_DELAY_MILLIS = 33;
+ private static final float CIRCLE_STEP = 16.0f;
+
+ @NonNull private final Surface surface;
+ private float circleX;
+ private float circleY;
+ private float radius;
+ private double angle;
+
+ Renderer(@NonNull Surface surface) {
+ this.surface = Assert.isNotNull(surface);
+ }
+
+ @Override
+ public void run() {
+ drawFrame();
+ schedule();
+ }
+
+ @WorkerThread
+ void schedule() {
+ Assert.isWorkerThread();
+ new Handler().postDelayed(this, FRAME_DELAY_MILLIS);
+ }
+
+ @WorkerThread
+ private void drawFrame() {
+ Assert.isWorkerThread();
+ Canvas canvas;
+ try {
+ canvas = surface.lockCanvas(null /* dirtyRect */);
+ } catch (IllegalArgumentException e) {
+ // This can happen when the video fragment tears down.
+ LogUtil.e("SimulatorRemoteVideo.RenderThread.drawFrame", "unable to lock canvas", e);
+ return;
+ }
+
+ LogUtil.i(
+ "SimulatorRemoteVideo.RenderThread.drawFrame",
+ "size; %d x %d",
+ canvas.getWidth(),
+ canvas.getHeight());
+ canvas.drawColor(Color.GREEN);
+ moveCircle(canvas);
+ drawCircle(canvas);
+ surface.unlockCanvasAndPost(canvas);
+ }
+
+ @WorkerThread
+ private void moveCircle(Canvas canvas) {
+ Assert.isWorkerThread();
+ int width = canvas.getWidth();
+ int height = canvas.getHeight();
+ if (circleX == 0 && circleY == 0) {
+ circleX = width / 2.0f;
+ circleY = height / 2.0f;
+ angle = Math.PI / 4.0;
+ radius = Math.min(canvas.getWidth(), canvas.getHeight()) * 0.15f;
+ } else {
+ circleX += (float) Math.cos(angle) * CIRCLE_STEP;
+ circleY += (float) Math.sin(angle) * CIRCLE_STEP;
+ // Bounce the circle off the edge.
+ if (circleX + radius >= width
+ || circleX - radius <= 0
+ || circleY + radius >= height
+ || circleY - radius <= 0) {
+ angle += Math.PI / 2.0;
+ }
+ }
+ }
+
+ @WorkerThread
+ private void drawCircle(Canvas canvas) {
+ Assert.isWorkerThread();
+ Paint paint = new Paint();
+ paint.setColor(Color.MAGENTA);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawCircle(circleX, circleY, radius, paint);
+ }
+ }
+
+ private static class RenderThread extends HandlerThread {
+ @NonNull private final Renderer renderer;
+
+ RenderThread(@NonNull Renderer renderer) {
+ super("SimulatorRemoteVideo");
+ this.renderer = Assert.isNotNull(renderer);
+ }
+
+ @Override
+ @WorkerThread
+ protected void onLooperPrepared() {
+ Assert.isWorkerThread();
+ renderer.schedule();
+ }
+
+ @VisibleForTesting
+ Runnable getRenderer() {
+ return renderer;
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
new file mode 100644
index 000000000..33eac51d1
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Utility to use the simulator connection service to add phone calls. To ensure that the added
+ * calls are routed through the simulator we register ourselves as a SIM call manager using
+ * CAPABILITY_CONNECTION_MANAGER. This ensures that all calls on the device must first go through
+ * our connection service.
+ *
+ * <p>For video calls this will only work if the underlying telephony phone account also supports
+ * video. To ensure that video always works we use a separate video account. The user must manually
+ * enable this account in call settings for video calls to work.
+ */
+public class SimulatorSimCallManager {
+
+ private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
+ private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID";
+ private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
+
+ static void register(@NonNull Context context) {
+ LogUtil.enterBlock("SimulatorSimCallManager.register");
+ Assert.isNotNull(context);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.registerPhoneAccount(buildSimCallManagerAccount(context));
+ telecomManager.registerPhoneAccount(buildVideoProviderAccount(context));
+ }
+
+ static void unregister(@NonNull Context context) {
+ LogUtil.enterBlock("SimulatorSimCallManager.unregister");
+ Assert.isNotNull(context);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.unregisterPhoneAccount(getSimCallManagerHandle(context));
+ telecomManager.unregisterPhoneAccount(getVideoProviderHandle(context));
+ }
+
+ @NonNull
+ public static String addNewOutgoingCall(
+ @NonNull Context context, @NonNull String phoneNumber, boolean isVideo) {
+ return addNewOutgoingCall(context, phoneNumber, isVideo, new Bundle());
+ }
+
+ @NonNull
+ public static String addNewOutgoingCall(
+ @NonNull Context context,
+ @NonNull String phoneNumber,
+ boolean isVideo,
+ @NonNull Bundle extras) {
+ LogUtil.enterBlock("SimulatorSimCallManager.addNewOutgoingCall");
+ Assert.isNotNull(context);
+ Assert.isNotNull(extras);
+ Assert.isNotNull(phoneNumber);
+ Assert.isNotNull(extras);
+
+ register(context);
+
+ extras = new Bundle(extras);
+ extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
+ String connectionTag = createUniqueConnectionTag();
+ extras.putBoolean(connectionTag, true);
+
+ Bundle outgoingCallExtras = new Bundle();
+ outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
+ outgoingCallExtras.putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context));
+
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ try {
+ telecomManager.placeCall(
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras);
+ } catch (SecurityException e) {
+ throw Assert.createIllegalStateFailException("Unable to place call: " + e);
+ }
+ return connectionTag;
+ }
+
+ @NonNull
+ public static String addNewIncomingCall(
+ @NonNull Context context, @NonNull String callerId, boolean isVideo) {
+ return addNewIncomingCall(context, callerId, isVideo, new Bundle());
+ }
+
+ @NonNull
+ public static String addNewIncomingCall(
+ @NonNull Context context, @NonNull String callerId, boolean isVideo, @NonNull Bundle extras) {
+ LogUtil.enterBlock("SimulatorSimCallManager.addNewIncomingCall");
+ Assert.isNotNull(context);
+ Assert.isNotNull(callerId);
+ Assert.isNotNull(extras);
+
+ register(context);
+
+ extras = new Bundle(extras);
+ extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId);
+ extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
+ String connectionTag = createUniqueConnectionTag();
+ extras.putBoolean(connectionTag, true);
+
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.addNewIncomingCall(
+ isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context), extras);
+ return connectionTag;
+ }
+
+ @NonNull
+ private static PhoneAccount buildSimCallManagerAccount(Context context) {
+ return new PhoneAccount.Builder(getSimCallManagerHandle(context), "Simulator SIM call manager")
+ .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+ .setShortDescription("Simulator SIM call manager")
+ .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+ .build();
+ }
+
+ @NonNull
+ private static PhoneAccount buildVideoProviderAccount(Context context) {
+ return new PhoneAccount.Builder(getVideoProviderHandle(context), "Simulator video provider")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING
+ | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .setShortDescription("Simulator video provider")
+ .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+ .build();
+ }
+
+ @NonNull
+ public static PhoneAccountHandle getSimCallManagerHandle(@NonNull Context context) {
+ return new PhoneAccountHandle(
+ new ComponentName(context, SimulatorConnectionService.class), SIM_CALL_MANAGER_ACCOUNT_ID);
+ }
+
+ @NonNull
+ static PhoneAccountHandle getVideoProviderHandle(@NonNull Context context) {
+ return new PhoneAccountHandle(
+ new ComponentName(context, SimulatorConnectionService.class), VIDEO_PROVIDER_ACCOUNT_ID);
+ }
+
+ @NonNull
+ private static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ List<PhoneAccountHandle> handles;
+ try {
+ handles = telecomManager.getCallCapablePhoneAccounts();
+ } catch (SecurityException e) {
+ throw Assert.createIllegalStateFailException("Unable to get phone accounts: " + e);
+ }
+ for (PhoneAccountHandle handle : handles) {
+ PhoneAccount account = telecomManager.getPhoneAccount(handle);
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ return handle;
+ }
+ }
+ throw Assert.createIllegalStateFailException("no SIM phone account available");
+ }
+
+ public static boolean isSimulatorConnectionRequest(@NonNull ConnectionRequest request) {
+ return request.getExtras() != null
+ && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION);
+ }
+
+ @NonNull
+ private static String createUniqueConnectionTag() {
+ int callId = new Random().nextInt();
+ return String.format("simulator_phone_call_%x", Math.abs(callId));
+ }
+
+ private SimulatorSimCallManager() {}
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
index ae97bc162..757658ddf 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
@@ -91,7 +91,7 @@ final class SimulatorSpamCallCreator implements SimulatorConnectionService.Liste
// We need to clear the call log because spam notifications are only shown for new calls.
clearCallLog(context);
- SimulatorConnectionService.addNewIncomingCall(context, extras, callerId);
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras);
}
private static boolean isSpamCallConnection(@NonNull Connection connection) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java b/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java
new file mode 100644
index 000000000..64a2e7265
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.ActionProvider;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import com.android.dialer.common.Assert;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Makes it easier to create submenus in the simulator. */
+final class SimulatorSubMenu extends ActionProvider {
+ List<Item> items = new ArrayList<>();
+
+ SimulatorSubMenu(@NonNull Context context) {
+ super(Assert.isNotNull(context));
+ }
+
+ SimulatorSubMenu addItem(@NonNull String title, @NonNull Runnable clickHandler) {
+ items.add(new Item(title, clickHandler));
+ return this;
+ }
+
+ SimulatorSubMenu addItem(@NonNull String title, @NonNull ActionProvider actionProvider) {
+ items.add(new Item(title, actionProvider));
+ return this;
+ }
+
+ @Override
+ public View onCreateActionView() {
+ return null;
+ }
+
+ @Override
+ public View onCreateActionView(MenuItem forItem) {
+ return null;
+ }
+
+ @Override
+ public boolean hasSubMenu() {
+ return true;
+ }
+
+ @Override
+ public void onPrepareSubMenu(SubMenu subMenu) {
+ super.onPrepareSubMenu(subMenu);
+ subMenu.clear();
+
+ for (Item item : items) {
+ if (item.clickHandler != null) {
+ subMenu
+ .add(item.title)
+ .setOnMenuItemClickListener(
+ (i) -> {
+ item.clickHandler.run();
+ return true;
+ });
+ } else {
+ subMenu.add(item.title).setActionProvider(item.actionProvider);
+ }
+ }
+ }
+
+ private static final class Item {
+ @NonNull final String title;
+ @Nullable final Runnable clickHandler;
+ @Nullable final ActionProvider actionProvider;
+
+ Item(@NonNull String title, @NonNull Runnable clickHandler) {
+ this.title = Assert.isNotNull(title);
+ this.clickHandler = Assert.isNotNull(clickHandler);
+ actionProvider = null;
+ }
+
+ Item(@NonNull String title, @NonNull ActionProvider actionProvider) {
+ this.title = Assert.isNotNull(title);
+ this.clickHandler = null;
+ this.actionProvider = Assert.isNotNull(actionProvider);
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
new file mode 100644
index 000000000..3f00ab183
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.view.ActionProvider;
+import android.widget.Toast;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.Simulator.Event;
+
+/** Entry point in the simulator to create video calls. */
+final class SimulatorVideoCall
+ implements SimulatorConnectionService.Listener, SimulatorConnection.Listener {
+ @NonNull private final Context context;
+ private final int initialVideoCapability;
+ private final int initialVideoState;
+ @Nullable private String connectionTag;
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem(
+ "Incoming one way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_RX_ENABLED).addNewIncomingCall())
+ .addItem(
+ "Incoming two way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_BIDIRECTIONAL)
+ .addNewIncomingCall())
+ .addItem(
+ "Outgoing one way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_TX_ENABLED).addNewOutgoingCall())
+ .addItem(
+ "Outgoing two way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_BIDIRECTIONAL)
+ .addNewOutgoingCall());
+ }
+
+ private SimulatorVideoCall(@NonNull Context context, int initialVideoState) {
+ this.context = Assert.isNotNull(context);
+ this.initialVideoCapability =
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL
+ | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL;
+ this.initialVideoState = initialVideoState;
+ SimulatorConnectionService.addListener(this);
+ }
+
+ private void addNewIncomingCall() {
+ if (!isVideoAccountEnabled()) {
+ showVideoAccountSettings();
+ return;
+ }
+ String callerId = "+44 (0) 20 7031 3000"; // Google London office
+ connectionTag =
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, true /* isVideo */);
+ }
+
+ private void addNewOutgoingCall() {
+ if (!isVideoAccountEnabled()) {
+ showVideoAccountSettings();
+ return;
+ }
+ String phoneNumber = "+44 (0) 20 7031 3000"; // Google London office
+ connectionTag =
+ SimulatorSimCallManager.addNewOutgoingCall(context, phoneNumber, true /* isVideo */);
+ }
+
+ @Override
+ public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVideoCall.onNewOutgoingConnection", "connection created");
+ handleNewConnection(connection);
+ // Telecom will force the connection to switch to Dialing when we return it. Wait until after
+ // we're returned it before changing call state.
+ ThreadUtil.postOnUiThread(() -> connection.setActive());
+ }
+ }
+
+ @Override
+ public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVideoCall.onNewIncomingConnection", "connection created");
+ handleNewConnection(connection);
+ }
+ }
+
+ private boolean isVideoAccountEnabled() {
+ SimulatorSimCallManager.register(context);
+ return context
+ .getSystemService(TelecomManager.class)
+ .getPhoneAccount(SimulatorSimCallManager.getVideoProviderHandle(context))
+ .isEnabled();
+ }
+
+ private void showVideoAccountSettings() {
+ context.startActivity(new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS));
+ Toast.makeText(context, "Please enable simulator video provider", Toast.LENGTH_LONG).show();
+ }
+
+ private void handleNewConnection(@NonNull SimulatorConnection connection) {
+ connection.addListener(this);
+ connection.setConnectionCapabilities(
+ connection.getConnectionCapabilities() | initialVideoCapability);
+ connection.setVideoState(initialVideoState);
+ }
+
+ @Override
+ public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
+ switch (event.type) {
+ case Event.NONE:
+ throw Assert.createIllegalStateFailException();
+ case Event.ANSWER:
+ connection.setVideoState(Integer.parseInt(event.data1));
+ connection.setActive();
+ break;
+ case Event.REJECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ break;
+ case Event.HOLD:
+ connection.setOnHold();
+ break;
+ case Event.UNHOLD:
+ connection.setActive();
+ break;
+ case Event.DISCONNECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ break;
+ case Event.STATE_CHANGE:
+ break;
+ case Event.DTMF:
+ break;
+ case Event.SESSION_MODIFY_REQUEST:
+ ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java b/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java
new file mode 100644
index 000000000..a596728a8
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.impl;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.simulator.Simulator.Event;
+
+/**
+ * Implements the telecom video provider API to simulate IMS video calling. A video capable phone
+ * always has one video provider associated with it. Actual drawing of local and remote video is
+ * done by {@link SimulatorPreviewCamera} and {@link SimulatorRemoteVideo} respectively.
+ */
+final class SimulatorVideoProvider extends Connection.VideoProvider {
+ @NonNull private final Context context;
+ @NonNull private final SimulatorConnection connection;
+ @Nullable private String previewCameraId;;
+ @Nullable private SimulatorPreviewCamera simulatorPreviewCamera;
+ @Nullable private SimulatorRemoteVideo simulatorRemoteVideo;
+
+ SimulatorVideoProvider(@NonNull Context context, @NonNull SimulatorConnection connection) {
+ this.context = Assert.isNotNull(context);
+ this.connection = Assert.isNotNull(connection);
+ }
+
+ @Override
+ public void onSetCamera(String previewCameraId) {
+ LogUtil.i("SimulatorVideoProvider.onSetCamera", "previewCameraId: " + previewCameraId);
+ this.previewCameraId = previewCameraId;
+ if (simulatorPreviewCamera != null) {
+ simulatorPreviewCamera.stopCamera();
+ simulatorPreviewCamera = null;
+ }
+ }
+
+ @Override
+ public void onSetPreviewSurface(Surface surface) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetPreviewSurface");
+ if (simulatorPreviewCamera != null) {
+ simulatorPreviewCamera.stopCamera();
+ simulatorPreviewCamera = null;
+ }
+ if (surface != null && previewCameraId != null) {
+ simulatorPreviewCamera = new SimulatorPreviewCamera(context, previewCameraId, surface);
+ simulatorPreviewCamera.startCamera();
+ }
+ }
+
+ @Override
+ public void onSetDisplaySurface(Surface surface) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetDisplaySurface");
+ if (simulatorRemoteVideo != null) {
+ simulatorRemoteVideo.stopVideo();
+ simulatorRemoteVideo = null;
+ }
+ if (surface != null) {
+ simulatorRemoteVideo = new SimulatorRemoteVideo(surface);
+ simulatorRemoteVideo.startVideo();
+ }
+ }
+
+ @Override
+ public void onSetDeviceOrientation(int rotation) {
+ LogUtil.i("SimulatorVideoProvider.onSetDeviceOrientation", "rotation: " + rotation);
+ }
+
+ @Override
+ public void onSetZoom(float value) {
+ LogUtil.i("SimulatorVideoProvider.onSetZoom", "zoom: " + value);
+ }
+
+ @Override
+ public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSendSessionModifyRequest");
+ connection.onEvent(
+ new Event(
+ Event.SESSION_MODIFY_REQUEST,
+ Integer.toString(fromProfile.getVideoState()),
+ Integer.toString(toProfile.getVideoState())));
+ }
+
+ @Override
+ public void onSendSessionModifyResponse(VideoProfile responseProfile) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSendSessionModifyResponse");
+ }
+
+ @Override
+ public void onRequestCameraCapabilities() {
+ LogUtil.enterBlock("SimulatorVideoProvider.onRequestCameraCapabilities");
+ changeCameraCapabilities(
+ SimulatorPreviewCamera.getCameraCapabilities(context, previewCameraId));
+ }
+
+ @Override
+ public void onRequestConnectionDataUsage() {
+ LogUtil.enterBlock("SimulatorVideoProvider.onRequestConnectionDataUsage");
+ setCallDataUsage(10 * 1024);
+ }
+
+ @Override
+ public void onSetPauseImage(Uri uri) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetPauseImage");
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index 2512828b8..8eefb48d9 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -17,18 +17,103 @@
package com.android.dialer.simulator.impl;
import android.content.Context;
-import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.view.ActionProvider;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.Simulator.Event;
-/** Utilities to simulate phone calls. */
-final class SimulatorVoiceCall {
- static void addNewIncomingCall(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorVoiceCall.addNewIncomingCall");
- // Set the caller ID to the Google London office.
- String callerId = "+44 (0) 20 7031 3000";
- SimulatorConnectionService.addNewIncomingCall(context, new Bundle(), callerId);
+/** Entry point in the simulator to create voice calls. */
+final class SimulatorVoiceCall
+ implements SimulatorConnectionService.Listener, SimulatorConnection.Listener {
+ @NonNull private final Context context;
+ @Nullable private String connectionTag;
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem("Incoming call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(false))
+ .addItem("Outgoing call", () -> new SimulatorVoiceCall(context).addNewOutgoingCall())
+ .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true));
+ }
+
+ private SimulatorVoiceCall(@NonNull Context context) {
+ this.context = Assert.isNotNull(context);
+ SimulatorConnectionService.addListener(this);
+ }
+
+ private void addNewIncomingCall(boolean isSpam) {
+ String callerId =
+ isSpam
+ ? "+1-661-778-3020" /* Blacklisted custom spam number */
+ : "+44 (0) 20 7031 3000" /* Google London office */;
+ connectionTag =
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */);
+ }
+
+ private void addNewOutgoingCall() {
+ String callerId = "+55-31-2128-6800"; // Brazil office.
+ connectionTag =
+ SimulatorSimCallManager.addNewOutgoingCall(context, callerId, false /* isVideo */);
}
- private SimulatorVoiceCall() {}
+ @Override
+ public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVoiceCall.onNewOutgoingConnection", "connection created");
+ handleNewConnection(connection);
+ connection.setActive();
+ }
+ }
+
+ @Override
+ public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVoiceCall.onNewIncomingConnection", "connection created");
+ handleNewConnection(connection);
+ }
+ }
+
+ private void handleNewConnection(@NonNull SimulatorConnection connection) {
+ connection.addListener(this);
+ connection.setConnectionCapabilities(
+ connection.getConnectionCapabilities()
+ | Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL
+ | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+ }
+
+ @Override
+ public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
+ switch (event.type) {
+ case Event.NONE:
+ throw Assert.createIllegalStateFailException();
+ case Event.ANSWER:
+ connection.setActive();
+ break;
+ case Event.REJECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ break;
+ case Event.HOLD:
+ connection.setOnHold();
+ break;
+ case Event.UNHOLD:
+ connection.setActive();
+ break;
+ case Event.DISCONNECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ break;
+ case Event.STATE_CHANGE:
+ break;
+ case Event.DTMF:
+ break;
+ case Event.SESSION_MODIFY_REQUEST:
+ ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
+ }
}