diff options
Diffstat (limited to 'java/com/android/dialer/simulator')
13 files changed, 1544 insertions, 0 deletions
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java new file mode 100644 index 000000000..78058a48f --- /dev/null +++ b/java/com/android/dialer/simulator/Simulator.java @@ -0,0 +1,27 @@ +/* + * 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; + +import android.content.Context; +import android.view.ActionProvider; + +/** Used to add menu items to the Dialer menu to test the app using simulated calls and data. */ +public interface Simulator { + boolean shouldShow(); + + ActionProvider getActionProvider(Context context); +} diff --git a/java/com/android/dialer/simulator/impl/AndroidManifest.xml b/java/com/android/dialer/simulator/impl/AndroidManifest.xml new file mode 100644 index 000000000..a30504d3f --- /dev/null +++ b/java/com/android/dialer/simulator/impl/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dialer.simulator.impl"> + + <application> + + <service + android:exported="true" + android:name=".SimulatorConnectionService" + android:permission="android.permission.BIND_CONNECTION_SERVICE"> + <intent-filter> + <action android:name="android.telecomm.ConnectionService"/> + </intent-filter> + </service> + + </application> + +</manifest> diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java new file mode 100644 index 000000000..591819819 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java @@ -0,0 +1,160 @@ +/* + * 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.support.annotation.NonNull; +import javax.annotation.Generated; + +@Generated("com.google.auto.value.processor.AutoValueProcessor") + final class AutoValue_SimulatorCallLog_CallEntry extends SimulatorCallLog.CallEntry { + + private final String number; + private final int type; + private final int presentation; + private final long timeMillis; + + private AutoValue_SimulatorCallLog_CallEntry( + String number, + int type, + int presentation, + long timeMillis) { + this.number = number; + this.type = type; + this.presentation = presentation; + this.timeMillis = timeMillis; + } + + @NonNull + @Override + String getNumber() { + return number; + } + + @Override + int getType() { + return type; + } + + @Override + int getPresentation() { + return presentation; + } + + @Override + long getTimeMillis() { + return timeMillis; + } + + @Override + public String toString() { + return "CallEntry{" + + "number=" + number + ", " + + "type=" + type + ", " + + "presentation=" + presentation + ", " + + "timeMillis=" + timeMillis + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof SimulatorCallLog.CallEntry) { + SimulatorCallLog.CallEntry that = (SimulatorCallLog.CallEntry) o; + return (this.number.equals(that.getNumber())) + && (this.type == that.getType()) + && (this.presentation == that.getPresentation()) + && (this.timeMillis == that.getTimeMillis()); + } + return false; + } + + @Override + public int hashCode() { + int h = 1; + h *= 1000003; + h ^= this.number.hashCode(); + h *= 1000003; + h ^= this.type; + h *= 1000003; + h ^= this.presentation; + h *= 1000003; + h ^= (this.timeMillis >>> 32) ^ this.timeMillis; + return h; + } + + static final class Builder extends SimulatorCallLog.CallEntry.Builder { + private String number; + private Integer type; + private Integer presentation; + private Long timeMillis; + Builder() { + } + private Builder(SimulatorCallLog.CallEntry source) { + this.number = source.getNumber(); + this.type = source.getType(); + this.presentation = source.getPresentation(); + this.timeMillis = source.getTimeMillis(); + } + @Override + SimulatorCallLog.CallEntry.Builder setNumber(String number) { + this.number = number; + return this; + } + @Override + SimulatorCallLog.CallEntry.Builder setType(int type) { + this.type = type; + return this; + } + @Override + SimulatorCallLog.CallEntry.Builder setPresentation(int presentation) { + this.presentation = presentation; + return this; + } + @Override + SimulatorCallLog.CallEntry.Builder setTimeMillis(long timeMillis) { + this.timeMillis = timeMillis; + return this; + } + @Override + SimulatorCallLog.CallEntry build() { + String missing = ""; + if (this.number == null) { + missing += " number"; + } + if (this.type == null) { + missing += " type"; + } + if (this.presentation == null) { + missing += " presentation"; + } + if (this.timeMillis == null) { + missing += " timeMillis"; + } + if (!missing.isEmpty()) { + throw new IllegalStateException("Missing required properties:" + missing); + } + return new AutoValue_SimulatorCallLog_CallEntry( + this.number, + this.type, + this.presentation, + this.timeMillis); + } + } + +} diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java new file mode 100644 index 000000000..00295f359 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java @@ -0,0 +1,231 @@ +/* + * 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.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.io.ByteArrayOutputStream; +import java.util.List; +import javax.annotation.Generated; + +@Generated("com.google.auto.value.processor.AutoValueProcessor") + final class AutoValue_SimulatorContacts_Contact extends SimulatorContacts.Contact { + + private final String accountType; + private final String accountName; + private final String name; + private final boolean isStarred; + private final ByteArrayOutputStream photoStream; + private final List<SimulatorContacts.PhoneNumber> phoneNumbers; + private final List<SimulatorContacts.Email> emails; + + private AutoValue_SimulatorContacts_Contact( + String accountType, + String accountName, + @Nullable String name, + boolean isStarred, + @Nullable ByteArrayOutputStream photoStream, + List<SimulatorContacts.PhoneNumber> phoneNumbers, + List<SimulatorContacts.Email> emails) { + this.accountType = accountType; + this.accountName = accountName; + this.name = name; + this.isStarred = isStarred; + this.photoStream = photoStream; + this.phoneNumbers = phoneNumbers; + this.emails = emails; + } + + @NonNull + @Override + String getAccountType() { + return accountType; + } + + @NonNull + @Override + String getAccountName() { + return accountName; + } + + @Nullable + @Override + String getName() { + return name; + } + + @Override + boolean getIsStarred() { + return isStarred; + } + + @Nullable + @Override + ByteArrayOutputStream getPhotoStream() { + return photoStream; + } + + @NonNull + @Override + List<SimulatorContacts.PhoneNumber> getPhoneNumbers() { + return phoneNumbers; + } + + @NonNull + @Override + List<SimulatorContacts.Email> getEmails() { + return emails; + } + + @Override + public String toString() { + return "Contact{" + + "accountType=" + accountType + ", " + + "accountName=" + accountName + ", " + + "name=" + name + ", " + + "isStarred=" + isStarred + ", " + + "photoStream=" + photoStream + ", " + + "phoneNumbers=" + phoneNumbers + ", " + + "emails=" + emails + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof SimulatorContacts.Contact) { + SimulatorContacts.Contact that = (SimulatorContacts.Contact) o; + return (this.accountType.equals(that.getAccountType())) + && (this.accountName.equals(that.getAccountName())) + && ((this.name == null) ? (that.getName() == null) : this.name.equals(that.getName())) + && (this.isStarred == that.getIsStarred()) + && ((this.photoStream == null) ? (that.getPhotoStream() == null) : this.photoStream.equals(that.getPhotoStream())) + && (this.phoneNumbers.equals(that.getPhoneNumbers())) + && (this.emails.equals(that.getEmails())); + } + return false; + } + + @Override + public int hashCode() { + int h = 1; + h *= 1000003; + h ^= this.accountType.hashCode(); + h *= 1000003; + h ^= this.accountName.hashCode(); + h *= 1000003; + h ^= (name == null) ? 0 : this.name.hashCode(); + h *= 1000003; + h ^= this.isStarred ? 1231 : 1237; + h *= 1000003; + h ^= (photoStream == null) ? 0 : this.photoStream.hashCode(); + h *= 1000003; + h ^= this.phoneNumbers.hashCode(); + h *= 1000003; + h ^= this.emails.hashCode(); + return h; + } + + static final class Builder extends SimulatorContacts.Contact.Builder { + private String accountType; + private String accountName; + private String name; + private Boolean isStarred; + private ByteArrayOutputStream photoStream; + private List<SimulatorContacts.PhoneNumber> phoneNumbers; + private List<SimulatorContacts.Email> emails; + Builder() { + } + private Builder(SimulatorContacts.Contact source) { + this.accountType = source.getAccountType(); + this.accountName = source.getAccountName(); + this.name = source.getName(); + this.isStarred = source.getIsStarred(); + this.photoStream = source.getPhotoStream(); + this.phoneNumbers = source.getPhoneNumbers(); + this.emails = source.getEmails(); + } + @Override + SimulatorContacts.Contact.Builder setAccountType(String accountType) { + this.accountType = accountType; + return this; + } + @Override + SimulatorContacts.Contact.Builder setAccountName(String accountName) { + this.accountName = accountName; + return this; + } + @Override + SimulatorContacts.Contact.Builder setName(@Nullable String name) { + this.name = name; + return this; + } + @Override + SimulatorContacts.Contact.Builder setIsStarred(boolean isStarred) { + this.isStarred = isStarred; + return this; + } + @Override + SimulatorContacts.Contact.Builder setPhotoStream(@Nullable ByteArrayOutputStream photoStream) { + this.photoStream = photoStream; + return this; + } + @Override + SimulatorContacts.Contact.Builder setPhoneNumbers(List<SimulatorContacts.PhoneNumber> phoneNumbers) { + this.phoneNumbers = phoneNumbers; + return this; + } + @Override + SimulatorContacts.Contact.Builder setEmails(List<SimulatorContacts.Email> emails) { + this.emails = emails; + return this; + } + @Override + SimulatorContacts.Contact build() { + String missing = ""; + if (this.accountType == null) { + missing += " accountType"; + } + if (this.accountName == null) { + missing += " accountName"; + } + if (this.isStarred == null) { + missing += " isStarred"; + } + if (this.phoneNumbers == null) { + missing += " phoneNumbers"; + } + if (this.emails == null) { + missing += " emails"; + } + if (!missing.isEmpty()) { + throw new IllegalStateException("Missing required properties:" + missing); + } + return new AutoValue_SimulatorContacts_Contact( + this.accountType, + this.accountName, + this.name, + this.isStarred, + this.photoStream, + this.phoneNumbers, + this.emails); + } + } + +} diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java new file mode 100644 index 000000000..58934801c --- /dev/null +++ b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java @@ -0,0 +1,184 @@ +/* + * 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.support.annotation.NonNull; +import javax.annotation.Generated; + +@Generated("com.google.auto.value.processor.AutoValueProcessor") + final class AutoValue_SimulatorVoicemail_Voicemail extends SimulatorVoicemail.Voicemail { + + private final String phoneNumber; + private final String transcription; + private final long durationSeconds; + private final long timeMillis; + private final boolean isRead; + + private AutoValue_SimulatorVoicemail_Voicemail( + String phoneNumber, + String transcription, + long durationSeconds, + long timeMillis, + boolean isRead) { + this.phoneNumber = phoneNumber; + this.transcription = transcription; + this.durationSeconds = durationSeconds; + this.timeMillis = timeMillis; + this.isRead = isRead; + } + + @NonNull + @Override + String getPhoneNumber() { + return phoneNumber; + } + + @NonNull + @Override + String getTranscription() { + return transcription; + } + + @Override + long getDurationSeconds() { + return durationSeconds; + } + + @Override + long getTimeMillis() { + return timeMillis; + } + + @Override + boolean getIsRead() { + return isRead; + } + + @Override + public String toString() { + return "Voicemail{" + + "phoneNumber=" + phoneNumber + ", " + + "transcription=" + transcription + ", " + + "durationSeconds=" + durationSeconds + ", " + + "timeMillis=" + timeMillis + ", " + + "isRead=" + isRead + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof SimulatorVoicemail.Voicemail) { + SimulatorVoicemail.Voicemail that = (SimulatorVoicemail.Voicemail) o; + return (this.phoneNumber.equals(that.getPhoneNumber())) + && (this.transcription.equals(that.getTranscription())) + && (this.durationSeconds == that.getDurationSeconds()) + && (this.timeMillis == that.getTimeMillis()) + && (this.isRead == that.getIsRead()); + } + return false; + } + + @Override + public int hashCode() { + int h = 1; + h *= 1000003; + h ^= this.phoneNumber.hashCode(); + h *= 1000003; + h ^= this.transcription.hashCode(); + h *= 1000003; + h ^= (this.durationSeconds >>> 32) ^ this.durationSeconds; + h *= 1000003; + h ^= (this.timeMillis >>> 32) ^ this.timeMillis; + h *= 1000003; + h ^= this.isRead ? 1231 : 1237; + return h; + } + + static final class Builder extends SimulatorVoicemail.Voicemail.Builder { + private String phoneNumber; + private String transcription; + private Long durationSeconds; + private Long timeMillis; + private Boolean isRead; + Builder() { + } + private Builder(SimulatorVoicemail.Voicemail source) { + this.phoneNumber = source.getPhoneNumber(); + this.transcription = source.getTranscription(); + this.durationSeconds = source.getDurationSeconds(); + this.timeMillis = source.getTimeMillis(); + this.isRead = source.getIsRead(); + } + @Override + SimulatorVoicemail.Voicemail.Builder setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + return this; + } + @Override + SimulatorVoicemail.Voicemail.Builder setTranscription(String transcription) { + this.transcription = transcription; + return this; + } + @Override + SimulatorVoicemail.Voicemail.Builder setDurationSeconds(long durationSeconds) { + this.durationSeconds = durationSeconds; + return this; + } + @Override + SimulatorVoicemail.Voicemail.Builder setTimeMillis(long timeMillis) { + this.timeMillis = timeMillis; + return this; + } + @Override + SimulatorVoicemail.Voicemail.Builder setIsRead(boolean isRead) { + this.isRead = isRead; + return this; + } + @Override + SimulatorVoicemail.Voicemail build() { + String missing = ""; + if (this.phoneNumber == null) { + missing += " phoneNumber"; + } + if (this.transcription == null) { + missing += " transcription"; + } + if (this.durationSeconds == null) { + missing += " durationSeconds"; + } + if (this.timeMillis == null) { + missing += " timeMillis"; + } + if (this.isRead == null) { + missing += " isRead"; + } + if (!missing.isEmpty()) { + throw new IllegalStateException("Missing required properties:" + missing); + } + return new AutoValue_SimulatorVoicemail_Voicemail( + this.phoneNumber, + this.transcription, + this.durationSeconds, + this.timeMillis, + this.isRead); + } + } + +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java b/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java new file mode 100644 index 000000000..6cd573361 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java @@ -0,0 +1,88 @@ +/* + * 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.os.AsyncTask; +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; + +/** Implements the simulator submenu. */ +final class SimulatorActionProvider extends ActionProvider { + @NonNull private final Context context; + + 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("Populate database") + .setOnMenuItemClickListener( + (item) -> { + populateDatabase(); + return true; + }); + } + + private void populateDatabase() { + new AsyncTask<Void, Void, Void>() { + @Override + public Void doInBackground(Void... params) { + SimulatorContacts.populateContacts(context); + SimulatorCallLog.populateCallLog(context); + SimulatorVoicemail.populateVoicemail(context); + return null; + } + }.execute(); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorCallLog.java b/java/com/android/dialer/simulator/impl/SimulatorCallLog.java new file mode 100644 index 000000000..9ace047d0 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorCallLog.java @@ -0,0 +1,139 @@ +/* + * 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.ContentProviderOperation; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.os.RemoteException; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import com.android.dialer.common.Assert; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +/** Populates the device database with call log entries. */ +final class SimulatorCallLog { + // Phone numbers from https://www.google.com/about/company/facts/locations/ + private static final CallEntry.Builder[] SIMPLE_CALL_LOG = { + CallEntry.builder().setType(Calls.MISSED_TYPE).setNumber("+1-302-6365454"), + CallEntry.builder() + .setType(Calls.MISSED_TYPE) + .setNumber("") + .setPresentation(Calls.PRESENTATION_UNKNOWN), + CallEntry.builder().setType(Calls.REJECTED_TYPE).setNumber("+1-302-6365454"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("+1-302-6365454"), + CallEntry.builder() + .setType(Calls.MISSED_TYPE) + .setNumber("1234") + .setPresentation(Calls.PRESENTATION_RESTRICTED), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+1-302-6365454"), + CallEntry.builder().setType(Calls.BLOCKED_TYPE).setNumber("+1-302-6365454"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("(425) 739-5600"), + CallEntry.builder().setType(Calls.ANSWERED_EXTERNALLY_TYPE).setNumber("(425) 739-5600"), + CallEntry.builder().setType(Calls.MISSED_TYPE).setNumber("+1 (425) 739-5600"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("739-5600"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("711"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("711"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("(425) 739-5600"), + CallEntry.builder().setType(Calls.MISSED_TYPE).setNumber("+44 (0) 20 7031 3000"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+1-650-2530000"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+1 303-245-0086;123,456"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+1 303-245-0086"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("+1-650-2530000"), + CallEntry.builder().setType(Calls.MISSED_TYPE).setNumber("650-2530000"), + CallEntry.builder().setType(Calls.REJECTED_TYPE).setNumber("2530000"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+1 404-487-9000"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("+61 2 9374 4001"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("+33 (0)1 42 68 53 00"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("972-74-746-6245"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("+971 4 4509500"), + CallEntry.builder().setType(Calls.INCOMING_TYPE).setNumber("+971 4 4509500"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("55-31-2128-6800"), + CallEntry.builder().setType(Calls.MISSED_TYPE).setNumber("611"), + CallEntry.builder().setType(Calls.OUTGOING_TYPE).setNumber("*86 512-343-5283"), + }; + + @WorkerThread + public static void populateCallLog(@NonNull Context context) { + Assert.isWorkerThread(); + ArrayList<ContentProviderOperation> operations = new ArrayList<>(); + // Do this 4 times to make the call log 4 times bigger. + long timeMillis = System.currentTimeMillis(); + for (int i = 0; i < 4; i++) { + for (CallEntry.Builder builder : SIMPLE_CALL_LOG) { + CallEntry callEntry = builder.setTimeMillis(timeMillis).build(); + operations.add( + ContentProviderOperation.newInsert(Calls.CONTENT_URI) + .withValues(callEntry.getAsContentValues()) + .withYieldAllowed(true) + .build()); + timeMillis -= TimeUnit.HOURS.toMillis(1); + } + } + try { + context.getContentResolver().applyBatch(CallLog.AUTHORITY, operations); + } catch (RemoteException | OperationApplicationException e) { + Assert.fail("error adding call entries: " + e); + } + } + + + abstract static class CallEntry { + @NonNull + abstract String getNumber(); + + abstract int getType(); + + abstract int getPresentation(); + + abstract long getTimeMillis(); + + static Builder builder() { + return new AutoValue_SimulatorCallLog_CallEntry.Builder() + .setPresentation(Calls.PRESENTATION_ALLOWED); + } + + ContentValues getAsContentValues() { + ContentValues values = new ContentValues(); + values.put(Calls.TYPE, getType()); + values.put(Calls.NUMBER, getNumber()); + values.put(Calls.NUMBER_PRESENTATION, getPresentation()); + values.put(Calls.DATE, getTimeMillis()); + return values; + } + + + abstract static class Builder { + abstract Builder setNumber(@NonNull String number); + + abstract Builder setType(int type); + + abstract Builder setPresentation(int presentation); + + abstract Builder setTimeMillis(long timeMillis); + + abstract CallEntry build(); + } + } + + private SimulatorCallLog() {} +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java new file mode 100644 index 000000000..12d095890 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java @@ -0,0 +1,56 @@ +/* + * 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.telecom.Connection; +import android.telecom.DisconnectCause; +import com.android.dialer.common.LogUtil; + +/** Represents a single phone call on the device. */ +final class SimulatorConnection extends Connection { + + @Override + public void onAnswer() { + LogUtil.enterBlock("SimulatorConnection.onAnswer"); + setActive(); + } + + @Override + public void onReject() { + LogUtil.enterBlock("SimulatorConnection.onReject"); + setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); + } + + @Override + public void onHold() { + LogUtil.enterBlock("SimulatorConnection.onHold"); + setOnHold(); + } + + @Override + public void onUnhold() { + LogUtil.enterBlock("SimulatorConnection.onUnhold"); + setActive(); + } + + @Override + public void onDisconnect() { + LogUtil.enterBlock("SimulatorConnection.onDisconnect"); + setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + destroy(); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java new file mode 100644 index 000000000..322360786 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java @@ -0,0 +1,87 @@ +/* + * 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.telecom.Connection; +import android.telecom.ConnectionRequest; +import android.telecom.ConnectionService; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; +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. */ +public final class SimulatorConnectionService extends ConnectionService { + + private static final String PHONE_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID"; + + public static void register(Context context) { + LogUtil.enterBlock("SimulatorConnectionService.register"); + context.getSystemService(TelecomManager.class).registerPhoneAccount(buildPhoneAccount(context)); + } + + 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) + .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 Connection onCreateOutgoingConnection( + PhoneAccountHandle phoneAccount, ConnectionRequest request) { + LogUtil.i( + "SimulatorConnectionService.onCreateOutgoingConnection", + "outgoing calls not supported yet"); + return null; + } + + @Override + public Connection onCreateIncomingConnection( + PhoneAccountHandle phoneAccount, ConnectionRequest request) { + LogUtil.enterBlock("SimulatorConnectionService.onCreateIncomingConnection"); + SimulatorConnection connection = new SimulatorConnection(); + connection.setRinging(); + connection.setAddress(getPhoneNumber(request), TelecomManager.PRESENTATION_ALLOWED); + connection.setConnectionCapabilities( + Connection.CAPABILITY_MUTE | Connection.CAPABILITY_SUPPORT_HOLD); + return connection; + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorContacts.java b/java/com/android/dialer/simulator/impl/SimulatorContacts.java new file mode 100644 index 000000000..89315094a --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorContacts.java @@ -0,0 +1,319 @@ +/* + * 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.ContentProviderOperation; +import android.content.Context; +import android.content.OperationApplicationException; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.text.TextUtils; +import com.android.dialer.common.Assert; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +/** Populates the device database with contacts. */ +final class SimulatorContacts { + // Phone numbers from https://www.google.com/about/company/facts/locations/ + private static final Contact[] SIMPLE_CONTACTS = { + // US, contact with e164 number. + Contact.builder() + .setName("Michelangelo") + .addPhoneNumber(new PhoneNumber("+1-302-6365454", Phone.TYPE_MOBILE)) + .addEmail(new Email("m@example.com")) + .setIsStarred(true) + .setOrangePhoto() + .build(), + // US, contact with a non-e164 number. + Contact.builder() + .setName("Leonardo da Vinci") + .addPhoneNumber(new PhoneNumber("(425) 739-5600", Phone.TYPE_MOBILE)) + .addEmail(new Email("l@example.com")) + .setIsStarred(true) + .setBluePhoto() + .build(), + // UK, number where the (0) should be dropped. + Contact.builder() + .setName("Raphael") + .addPhoneNumber(new PhoneNumber("+44 (0) 20 7031 3000", Phone.TYPE_MOBILE)) + .addEmail(new Email("r@example.com")) + .setIsStarred(true) + .setRedPhoto() + .build(), + // US and Australia, contact with a long name and multiple phone numbers. + Contact.builder() + .setName("Donatello di Niccolò di Betto Bardi") + .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) + .addPhoneNumber(new PhoneNumber("+1 404-487-9000", Phone.TYPE_WORK)) + .addPhoneNumber(new PhoneNumber("+61 2 9374 4001", Phone.TYPE_FAX_HOME)) + .setIsStarred(true) + .setPurplePhoto() + .build(), + // US, phone number shared with another contact and 2nd phone number with wait and pause. + Contact.builder() + .setName("Splinter") + .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) + .addPhoneNumber(new PhoneNumber("+1 303-245-0086;123,456", Phone.TYPE_WORK)) + .build(), + // France, number with Japanese name. + Contact.builder() + .setName("スパイク・スピーゲル") + .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) + .build(), + // Israel, RTL name and non-e164 number. + Contact.builder() + .setName("עקב אריה טברסק") + .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) + .build(), + // UAE, RTL name. + Contact.builder() + .setName("سلام دنیا") + .addPhoneNumber(new PhoneNumber("+971 4 4509500", Phone.TYPE_MOBILE)) + .build(), + // Brazil, contact with no name. + Contact.builder() + .addPhoneNumber(new PhoneNumber("+55-31-2128-6800", Phone.TYPE_MOBILE)) + .build(), + // Short number, contact with no name. + Contact.builder().addPhoneNumber(new PhoneNumber("611", Phone.TYPE_MOBILE)).build(), + // US, number with an anonymous prefix. + Contact.builder() + .setName("Anonymous") + .addPhoneNumber(new PhoneNumber("*86 512-343-5283", Phone.TYPE_MOBILE)) + .build(), + // None, contact with no phone number. + Contact.builder() + .setName("No Phone Number") + .addEmail(new Email("no@example.com")) + .setIsStarred(true) + .build(), + }; + + @WorkerThread + static void populateContacts(@NonNull Context context) { + Assert.isWorkerThread(); + ArrayList<ContentProviderOperation> operations = new ArrayList<>(); + for (Contact contact : SIMPLE_CONTACTS) { + addContact(contact, operations); + } + try { + context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); + } catch (RemoteException | OperationApplicationException e) { + Assert.fail("error adding contacts: " + e); + } + } + + private static void addContact(Contact contact, List<ContentProviderOperation> operations) { + int index = operations.size(); + + operations.add( + ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType()) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName()) + .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred()) + .withYieldAllowed(true) + .build()); + + if (!TextUtils.isEmpty(contact.getName())) { + operations.add( + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue( + ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName()) + .build()); + } + + if (contact.getPhotoStream() != null) { + operations.add( + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) + .withValue( + ContactsContract.CommonDataKinds.Photo.PHOTO, + contact.getPhotoStream().toByteArray()) + .build()); + } + + for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) { + operations.add( + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type) + .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label) + .build()); + } + + for (Email email : contact.getEmails()) { + operations.add( + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type) + .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label) + .build()); + } + } + + + abstract static class Contact { + @NonNull + abstract String getAccountType(); + + @NonNull + abstract String getAccountName(); + + @Nullable + abstract String getName(); + + abstract boolean getIsStarred(); + + @Nullable + abstract ByteArrayOutputStream getPhotoStream(); + + @NonNull + abstract List<PhoneNumber> getPhoneNumbers(); + + @NonNull + abstract List<Email> getEmails(); + + static Builder builder() { + return new AutoValue_SimulatorContacts_Contact.Builder() + .setAccountType("com.google") + .setAccountName("foo@example") + .setIsStarred(false) + .setPhoneNumbers(new ArrayList<>()) + .setEmails(new ArrayList<>()); + } + + + abstract static class Builder { + @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>(); + @NonNull private final List<Email> emails = new ArrayList<>(); + + abstract Builder setAccountType(@NonNull String accountType); + + abstract Builder setAccountName(@NonNull String accountName); + + abstract Builder setName(@NonNull String name); + + abstract Builder setIsStarred(boolean isStarred); + + abstract Builder setPhotoStream(ByteArrayOutputStream photoStream); + + abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers); + + abstract Builder setEmails(@NonNull List<Email> emails); + + abstract Contact build(); + + Builder addPhoneNumber(PhoneNumber phoneNumber) { + phoneNumbers.add(phoneNumber); + return setPhoneNumbers(phoneNumbers); + } + + Builder addEmail(Email email) { + emails.add(email); + return setEmails(emails); + } + + Builder setRedPhoto() { + setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c))); + return this; + } + + Builder setBluePhoto() { + setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6))); + return this; + } + + Builder setOrangePhoto() { + setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00))); + return this; + } + + Builder setPurplePhoto() { + setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0))); + return this; + } + + /** Creates a contact photo with a green background and a circle of the given color. */ + private static ByteArrayOutputStream getPhotoStreamWithColor(int color) { + int width = 300; + int height = 300; + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23)); + Paint paint = new Paint(); + paint.setColor(color); + paint.setStyle(Paint.Style.FILL); + canvas.drawCircle(width / 2, height / 2, width / 3, paint); + + ByteArrayOutputStream photoStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream); + return photoStream; + } + } + } + + static class PhoneNumber { + public final String value; + public final int type; + public final String label; + + PhoneNumber(String value, int type) { + this.value = value; + this.type = type; + label = "simulator phone number"; + } + } + + static class Email { + public final String value; + public final int type; + public final String label; + + Email(String simpleEmail) { + value = simpleEmail; + type = ContactsContract.CommonDataKinds.Email.TYPE_WORK; + label = "simulator email"; + } + } + + private SimulatorContacts() {} +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorModule.java b/java/com/android/dialer/simulator/impl/SimulatorModule.java new file mode 100644 index 000000000..0f8ad3954 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorModule.java @@ -0,0 +1,34 @@ +/* + * 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.view.ActionProvider; +import com.android.dialer.simulator.Simulator; + +/** The entry point for the simulator module. */ +public final class SimulatorModule implements Simulator { + @Override + public boolean shouldShow() { + return true; + } + + @Override + public ActionProvider getActionProvider(Context context) { + return new SimulatorActionProvider(context); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java new file mode 100644 index 000000000..39c1d02a5 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -0,0 +1,47 @@ +/* + * 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.os.Bundle; +import android.support.annotation.NonNull; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; + +/** Utilities to simulate phone calls. */ +final class SimulatorVoiceCall { + public static void addNewIncomingCall(@NonNull Context context) { + LogUtil.enterBlock("SimulatorVoiceCall.addNewIncomingCall"); + SimulatorConnectionService.register(context); + + Bundle bundle = new Bundle(); + // Set the caller ID to the Google London office. + bundle.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, "+44 (0) 20 7031 3000"); + try { + context + .getSystemService(TelecomManager.class) + .addNewIncomingCall( + SimulatorConnectionService.getConnectionServiceHandle(context), bundle); + } catch (SecurityException e) { + Assert.fail("unable to add call: " + e); + } + } + + private SimulatorVoiceCall() {} +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java b/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java new file mode 100644 index 000000000..ffb9191dc --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java @@ -0,0 +1,154 @@ +/* + * 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.ContentValues; +import android.content.Context; +import android.provider.VoicemailContract.Status; +import android.provider.VoicemailContract.Voicemails; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import android.telecom.PhoneAccountHandle; +import android.telephony.TelephonyManager; +import com.android.dialer.common.Assert; + +import java.util.concurrent.TimeUnit; + +/** Populates the device database with voicemail entries. */ +final class SimulatorVoicemail { + private static final String ACCOUNT_ID = "ACCOUNT_ID"; + + private static final Voicemail.Builder[] SIMPLE_VOICEMAILS = { + // Long transcription with an embedded phone number. + Voicemail.builder() + .setPhoneNumber("+1-302-6365454") + .setTranscription( + "Hi, this is a very long voicemail. Please call me back at 650 253 0000. " + + "I hope you listen to all of it. This is very important. " + + "Hi, this is a very long voicemail. " + + "I hope you listen to all of it. It's very important.") + .setDurationSeconds(10) + .setIsRead(false), + // RTL transcription. + Voicemail.builder() + .setPhoneNumber("+1-302-6365454") + .setTranscription("هزاران دوست کم اند و یک دشمن زیاد") + .setDurationSeconds(60) + .setIsRead(true), + // Empty number. + Voicemail.builder() + .setPhoneNumber("") + .setTranscription("") + .setDurationSeconds(60) + .setIsRead(true), + // No duration. + Voicemail.builder() + .setPhoneNumber("+1-302-6365454") + .setTranscription("") + .setDurationSeconds(0) + .setIsRead(true), + // Short number. + Voicemail.builder() + .setPhoneNumber("711") + .setTranscription("This is a short voicemail.") + .setDurationSeconds(12) + .setIsRead(true), + }; + + @WorkerThread + public static void populateVoicemail(@NonNull Context context) { + Assert.isWorkerThread(); + enableVoicemail(context); + + // Do this 4 times to make the voicemail database 4 times bigger. + long timeMillis = System.currentTimeMillis(); + for (int i = 0; i < 4; i++) { + for (Voicemail.Builder builder : SIMPLE_VOICEMAILS) { + Voicemail voicemail = builder.setTimeMillis(timeMillis).build(); + context + .getContentResolver() + .insert( + Voicemails.buildSourceUri(context.getPackageName()), + voicemail.getAsContentValues(context)); + timeMillis -= TimeUnit.HOURS.toMillis(2); + } + } + } + + private static void enableVoicemail(@NonNull Context context) { + PhoneAccountHandle handle = + new PhoneAccountHandle(new ComponentName(context, SimulatorVoicemail.class), ACCOUNT_ID); + + ContentValues values = new ContentValues(); + values.put(Status.SOURCE_PACKAGE, handle.getComponentName().getPackageName()); + values.put(Status.SOURCE_TYPE, TelephonyManager.VVM_TYPE_OMTP); + values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME, handle.getComponentName().flattenToString()); + values.put(Status.PHONE_ACCOUNT_ID, handle.getId()); + values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK); + values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK); + values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK); + context.getContentResolver().insert(Status.buildSourceUri(context.getPackageName()), values); + } + + + abstract static class Voicemail { + @NonNull + abstract String getPhoneNumber(); + + @NonNull + abstract String getTranscription(); + + abstract long getDurationSeconds(); + + abstract long getTimeMillis(); + + abstract boolean getIsRead(); + + static Builder builder() { + return new AutoValue_SimulatorVoicemail_Voicemail.Builder(); + } + + ContentValues getAsContentValues(Context context) { + ContentValues values = new ContentValues(); + values.put(Voicemails.DATE, getTimeMillis()); + values.put(Voicemails.NUMBER, getPhoneNumber()); + values.put(Voicemails.DURATION, getDurationSeconds()); + values.put(Voicemails.SOURCE_PACKAGE, context.getPackageName()); + values.put(Voicemails.IS_READ, getIsRead() ? 1 : 0); + values.put(Voicemails.TRANSCRIPTION, getTranscription()); + return values; + } + + + abstract static class Builder { + abstract Builder setPhoneNumber(@NonNull String phoneNumber); + + abstract Builder setTranscription(@NonNull String transcription); + + abstract Builder setDurationSeconds(long durationSeconds); + + abstract Builder setTimeMillis(long timeMillis); + + abstract Builder setIsRead(boolean isRead); + + abstract Voicemail build(); + } + } + + private SimulatorVoicemail() {} +} |