From 64f9ed258a852781422c7d70eaeaf1a37cde51a7 Mon Sep 17 00:00:00 2001 From: wangqi Date: Fri, 2 Feb 2018 16:16:04 -0800 Subject: Update text on after call spam notification and dialog. This change also includes: 1. Fixes formatting of phone number in the dialog. 2. Remove body text if the dialog. 3. Update to material design dialog style. Bug: 72120616 Test: manual PiperOrigin-RevId: 184350133 Change-Id: I8bdd9c792877822fe18ba6aae6c04136a603dbba --- java/com/android/incallui/res/values/styles.xml | 4 +- .../incallui/spam/SpamCallListListener.java | 5 +-- .../incallui/spam/SpamNotificationActivity.java | 44 ++++++++++++---------- .../android/incallui/spam/res/values/strings.xml | 16 +++----- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/java/com/android/incallui/res/values/styles.xml b/java/com/android/incallui/res/values/styles.xml index cfa4dd6f0..269b72111 100644 --- a/java/com/android/incallui/res/values/styles.xml +++ b/java/com/android/incallui/res/values/styles.xml @@ -67,14 +67,14 @@ 10dp - - diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java index e7603f041..c7fa498e1 100644 --- a/java/com/android/incallui/spam/SpamCallListListener.java +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -391,14 +391,11 @@ public class SpamCallListListener implements CallList.Listener { createAfterCallNotificationBuilder(call) .setLargeIcon(Icon.createWithResource(context, R.drawable.spam_notification_icon)) .setContentText(context.getString(R.string.spam_notification_spam_call_collapsed_text)) - .setStyle( - new Notification.BigTextStyle() - .bigText(context.getString(R.string.spam_notification_spam_call_expanded_text))) // Not spam .addAction( new Notification.Action.Builder( R.drawable.quantum_ic_close_vd_theme_24, - context.getString(R.string.spam_notification_not_spam_action_text), + context.getString(R.string.spam_notification_was_not_spam_action_text), createNotSpamPendingIntent(call)) .build()) // Block/report spam diff --git a/java/com/android/incallui/spam/SpamNotificationActivity.java b/java/com/android/incallui/spam/SpamNotificationActivity.java index ceb9ebb90..f6da2a3b0 100644 --- a/java/com/android/incallui/spam/SpamNotificationActivity.java +++ b/java/com/android/incallui/spam/SpamNotificationActivity.java @@ -18,14 +18,15 @@ package com.android.incallui.spam; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.provider.CallLog; import android.provider.ContactsContract; -import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.telephony.PhoneNumberUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.blocking.BlockReportSpamDialogs; import com.android.dialer.blocking.BlockedNumbersMigrator; @@ -106,8 +107,14 @@ public class SpamNotificationActivity extends FragmentActivity { } /** Returns the formatted version of the given number. */ - private static String getFormattedNumber(String number) { - return PhoneNumberUtilsCompat.createTtsSpannable(number).toString(); + private static String getFormattedNumber(String number, Context context) { + String formattedNumber = + PhoneNumberUtils.formatNumber(number, GeoUtil.getCurrentCountryIso(context)); + return PhoneNumberUtilsCompat.createTtsSpannable(formattedNumber).toString(); + } + + private void logCallImpression(DialerImpression.Type impression) { + logCallImpression(this, getCallInfo(), impression); } private static void logCallImpression( @@ -190,7 +197,7 @@ public class SpamNotificationActivity extends FragmentActivity { final String number, final ContactLookupResult.Type contactLookupResultType) { if (SpamComponent.get(this).spam().isDialogEnabledForSpamNotification()) { BlockReportSpamDialogs.ReportNotSpamDialogFragment.newInstance( - getFormattedNumber(number), + getFormattedNumber(number, this), new BlockReportSpamDialogs.OnConfirmListener() { @Override public void onClick() { @@ -208,12 +215,13 @@ public class SpamNotificationActivity extends FragmentActivity { private void maybeShowBlockReportSpamDialog( final String number, final ContactLookupResult.Type contactLookupResultType) { if (SpamComponent.get(this).spam().isDialogEnabledForSpamNotification()) { + String displayNumber = getFormattedNumber(number, this); maybeShowBlockNumberMigrationDialog( new BlockedNumbersMigrator.Listener() { @Override public void onComplete() { BlockReportSpamDialogs.BlockReportSpamDialogFragment.newInstance( - getFormattedNumber(number), + displayNumber, SpamComponent.get(SpamNotificationActivity.this) .spam() .isDialogReportSpamCheckedByDefault(), @@ -240,7 +248,7 @@ public class SpamNotificationActivity extends FragmentActivity { private void showNonSpamDialog() { logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_SHOW_NON_SPAM_DIALOG); FirstTimeNonSpamCallDialogFragment.newInstance(getCallInfo()) - .show(getSupportFragmentManager(), FirstTimeNonSpamCallDialogFragment.TAG); + .show(getFragmentManager(), FirstTimeNonSpamCallDialogFragment.TAG); } /** @@ -249,7 +257,7 @@ public class SpamNotificationActivity extends FragmentActivity { private void showSpamFullDialog() { logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_SHOW_SPAM_DIALOG); FirstTimeSpamCallDialogFragment.newInstance(getCallInfo()) - .show(getSupportFragmentManager(), FirstTimeSpamCallDialogFragment.TAG); + .show(getFragmentManager(), FirstTimeSpamCallDialogFragment.TAG); } /** Checks if the user has migrated to the new blocking and display a dialog if necessary. */ @@ -319,10 +327,6 @@ public class SpamNotificationActivity extends FragmentActivity { return getIntent().getBundleExtra(EXTRA_CALL_INFO); } - private void logCallImpression(DialerImpression.Type impression) { - logCallImpression(this, getCallInfo(), impression); - } - /** Dialog that displays "Not spam", "Block/report spam" and "Dismiss". */ public static class FirstTimeSpamCallDialogFragment extends DialogFragment { @@ -374,8 +378,9 @@ public class SpamNotificationActivity extends FragmentActivity { return new AlertDialog.Builder(getActivity()) .setCancelable(false) - .setTitle(getString(R.string.spam_notification_title, getFormattedNumber(number))) - .setMessage(getString(R.string.spam_notification_spam_call_expanded_text)) + .setTitle( + getString( + R.string.spam_notification_title, getFormattedNumber(number, applicationContext))) .setNeutralButton( getString(R.string.spam_notification_action_dismiss), new DialogInterface.OnClickListener() { @@ -385,24 +390,24 @@ public class SpamNotificationActivity extends FragmentActivity { } }) .setPositiveButton( - getString(R.string.spam_notification_dialog_was_not_spam_action_text), + getString(R.string.spam_notification_block_spam_action_text), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissed = true; dismiss(); - spamNotificationActivity.maybeShowNotSpamDialog(number, contactLookupResultType); + spamNotificationActivity.maybeShowBlockReportSpamDialog( + number, contactLookupResultType); } }) .setNegativeButton( - getString(R.string.spam_notification_block_spam_action_text), + getString(R.string.spam_notification_was_not_spam_action_text), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissed = true; dismiss(); - spamNotificationActivity.maybeShowBlockReportSpamDialog( - number, contactLookupResultType); + spamNotificationActivity.maybeShowNotSpamDialog(number, contactLookupResultType); } }) .create(); @@ -459,7 +464,8 @@ public class SpamNotificationActivity extends FragmentActivity { ContactLookupResult.Type.forNumber( getArguments().getInt(CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE, 0)); return new AlertDialog.Builder(getActivity()) - .setTitle(getString(R.string.non_spam_notification_title, getFormattedNumber(number))) + .setTitle( + getString(R.string.non_spam_notification_title, getFormattedNumber(number, context))) .setCancelable(false) .setMessage(getString(R.string.spam_notification_non_spam_call_expanded_text)) .setNeutralButton( diff --git a/java/com/android/incallui/spam/res/values/strings.xml b/java/com/android/incallui/spam/res/values/strings.xml index a07057db9..3248c69ac 100644 --- a/java/com/android/incallui/spam/res/values/strings.xml +++ b/java/com/android/incallui/spam/res/values/strings.xml @@ -20,7 +20,7 @@ Know %1$s? - Is %1$s spam? + Was %1$s a spam call? %1$s blocked and call was reported as spam. @@ -30,23 +30,19 @@ This is the first time this number called you. If this call was spam, you can block this number and report it. - Tap to report as NOT SPAM, or block it. - - We suspected this to be a spammer. If this call wasn\'t spam, tap "NOT SPAM" to report our mistake. + Tap to report as not spam or block it Block & report Add contact - - Not spam - - Block number Add to contacts Block & report spam - - Not spam + + No, not spam + + Yes, block number Dismiss -- cgit v1.2.3 From a792792df68ef0fecad8056a1709cacbe0f474ac Mon Sep 17 00:00:00 2001 From: erfanian Date: Fri, 2 Feb 2018 16:36:11 -0800 Subject: Update the SpeakEasy component. Test: unit tests PiperOrigin-RevId: 184352489 Change-Id: If3f2414dd9769dc54035c7fcf5afe925af3006db --- .../dialer/simulator/impl/SimulatorMainMenu.java | 13 ------ java/com/android/incallui/InCallServiceImpl.java | 15 +++++++ java/com/android/incallui/speakeasy/SpeakEasy.java | 39 ------------------ .../incallui/speakeasy/SpeakEasyActivity.java | 47 ---------------------- .../incallui/speakeasy/SpeakEasyCallManager.java | 40 ++++++++++++++++++ .../speakeasy/SpeakEasyCallManagerStub.java | 41 +++++++++++++++++++ .../incallui/speakeasy/SpeakEasyComponent.java | 4 +- .../android/incallui/speakeasy/SpeakEasyStub.java | 39 ------------------ .../incallui/speakeasy/StubSpeakEasyModule.java | 4 +- 9 files changed, 100 insertions(+), 142 deletions(-) delete mode 100644 java/com/android/incallui/speakeasy/SpeakEasy.java delete mode 100644 java/com/android/incallui/speakeasy/SpeakEasyActivity.java create mode 100644 java/com/android/incallui/speakeasy/SpeakEasyCallManager.java create mode 100644 java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java delete mode 100644 java/com/android/incallui/speakeasy/SpeakEasyStub.java diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java index 69da2f4cc..0bd1c0f22 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java +++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java @@ -34,9 +34,6 @@ import com.android.dialer.persistentlog.PersistentLogger; import com.android.dialer.preferredsim.PreferredSimFallbackContract; import com.android.dialer.simulator.SimulatorComponent; import com.android.incallui.rtt.impl.RttChatActivity; -import com.android.incallui.speakeasy.SpeakEasy; -import com.android.incallui.speakeasy.SpeakEasyActivity; -import com.android.incallui.speakeasy.SpeakEasyComponent; /** Implements the top level simulator menu. */ final class SimulatorMainMenu { @@ -79,12 +76,6 @@ final class SimulatorMainMenu { SimulatorComponent.get(activity.getApplicationContext()) .getSimulator() .disableSimulatorMode()); - SpeakEasy speakEasy = SpeakEasyComponent.get(activity.getApplicationContext()).speakEasy(); - if (speakEasy.isEnabled()) { - simulatorSubMenu.addItem( - "SpeakEasy call mock", () -> simulateSpeakEasyCallMock(activity.getApplicationContext())); - } - return simulatorSubMenu; } @@ -92,10 +83,6 @@ final class SimulatorMainMenu { context.startActivity(new Intent(context, RttChatActivity.class)); } - private static void simulateSpeakEasyCallMock(@NonNull Context context) { - context.startActivity(new Intent(context, SpeakEasyActivity.class)); - } - private static void populateDatabase(@NonNull Context context) { DialerExecutorComponent.get(context) .dialerExecutorFactory() diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java index 1cb6c478e..c4d6d064f 100644 --- a/java/com/android/incallui/InCallServiceImpl.java +++ b/java/com/android/incallui/InCallServiceImpl.java @@ -29,6 +29,8 @@ import com.android.incallui.audiomode.AudioModeProvider; import com.android.incallui.call.CallList; import com.android.incallui.call.ExternalCallList; import com.android.incallui.call.TelecomAdapter; +import com.android.incallui.speakeasy.SpeakEasyCallManager; +import com.android.incallui.speakeasy.SpeakEasyComponent; /** * Used to receive updates about calls from the Telecom component. This service is bound to Telecom @@ -41,6 +43,11 @@ public class InCallServiceImpl extends InCallService { private ReturnToCallController returnToCallController; private NewReturnToCallController newReturnToCallController; private CallList.Listener feedbackListener; + // We only expect there to be one speakEasyCallManager to be instantiated at a time. + // We did not use a singleton SpeakEasyCallManager to avoid holding on to state beyond the + // lifecycle of this service, because the singleton is associated with the state of the + // Application, not this service. + private SpeakEasyCallManager speakEasyCallManager; @Override public void onCallAudioStateChanged(CallAudioState audioState) { @@ -66,6 +73,8 @@ public class InCallServiceImpl extends InCallService { @Override public void onCallRemoved(Call call) { Trace.beginSection("InCallServiceImpl.onCallRemoved"); + speakEasyCallManager.onCallRemoved(CallList.getInstance().getDialerCallFromTelecomCall(call)); + InCallPresenter.getInstance().onCallRemoved(call); Trace.endSection(); } @@ -77,6 +86,12 @@ public class InCallServiceImpl extends InCallService { Trace.endSection(); } + @Override + public void onCreate() { + super.onCreate(); + this.speakEasyCallManager = SpeakEasyComponent.get(this).speakEasyCallManager(); + } + @Override public IBinder onBind(Intent intent) { Trace.beginSection("InCallServiceImpl.onBind"); diff --git a/java/com/android/incallui/speakeasy/SpeakEasy.java b/java/com/android/incallui/speakeasy/SpeakEasy.java deleted file mode 100644 index 5621eed47..000000000 --- a/java/com/android/incallui/speakeasy/SpeakEasy.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.incallui.speakeasy; - -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; - -/** This interface provides a wrapper between callers and the Whisper client. */ -public interface SpeakEasy { - - /** Signals to the user interface that the feature is available for use. */ - boolean isEnabled(); - - /** - * Create a new instance of SpeakEasy fragment. - * - * @param callId call id of the call. - * @param nameOrNumber name or number of the caller to be displayed - * @param sessionStartTimeMillis start time of the session in terms of {@link - * android.os.SystemClock#elapsedRealtime}. - * @return new SpeakEasy fragment. Null if the SpeakEasy feature is not available for use - */ - @Nullable - Fragment getSpeakEasyFragment(String callId, String nameOrNumber, long sessionStartTimeMillis); -} diff --git a/java/com/android/incallui/speakeasy/SpeakEasyActivity.java b/java/com/android/incallui/speakeasy/SpeakEasyActivity.java deleted file mode 100644 index 46635265c..000000000 --- a/java/com/android/incallui/speakeasy/SpeakEasyActivity.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.incallui.speakeasy; - -import android.os.Bundle; -import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.view.View; - -/** Activity to for SpeakEasy component. */ -public class SpeakEasyActivity extends FragmentActivity { - - private SpeakEasy speakEasy; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - speakEasy = SpeakEasyComponent.get(this).speakEasy(); - setContentView(R.layout.activity_speakeasy); - Fragment speakEasyFragment = - speakEasy.getSpeakEasyFragment("", "John Snow", SystemClock.elapsedRealtime()); - if (speakEasyFragment != null) { - getSupportFragmentManager() - .beginTransaction() - .add(R.id.fragment_speakeasy, speakEasyFragment) - .commit(); - } - getWindow().setStatusBarColor(getColor(R.color.speakeasy_status_bar_color)); - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); - } -} diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java new file mode 100644 index 000000000..4fe894a38 --- /dev/null +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.speakeasy; + +import android.app.Fragment; +import android.support.annotation.NonNull; +import com.android.incallui.call.DialerCall; +import com.google.common.base.Optional; + +/** Provides operations necessary to SpeakEasy. */ +public interface SpeakEasyCallManager { + + /** + * Returns the Fragment used to display data. + * + *

An absent optional indicates the feature is unavailable. + */ + Optional getSpeakEasyFragment(@NonNull DialerCall call); + + /** + * Indicates a call has been removed. + * + * @param call The call which has been removed. + */ + void onCallRemoved(@NonNull DialerCall call); +} diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java new file mode 100644 index 000000000..e84766c71 --- /dev/null +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.speakeasy; + +import android.app.Fragment; +import android.support.annotation.Nullable; +import com.android.incallui.call.DialerCall; +import com.google.common.base.Optional; +import javax.inject.Inject; + +/** Default implementation of SpeakEasyCallManager. */ +public class SpeakEasyCallManagerStub implements SpeakEasyCallManager { + + @Inject + public SpeakEasyCallManagerStub() {} + + /** Returns an absent optional. */ + @Override + @Nullable + public Optional getSpeakEasyFragment(DialerCall call) { + return Optional.absent(); + } + + /** Always inert in the stub. */ + @Override + public void onCallRemoved(DialerCall call) {} +} diff --git a/java/com/android/incallui/speakeasy/SpeakEasyComponent.java b/java/com/android/incallui/speakeasy/SpeakEasyComponent.java index 13f5e97fe..2403354bc 100644 --- a/java/com/android/incallui/speakeasy/SpeakEasyComponent.java +++ b/java/com/android/incallui/speakeasy/SpeakEasyComponent.java @@ -20,11 +20,11 @@ import android.content.Context; import com.android.dialer.inject.HasRootComponent; import dagger.Subcomponent; -/** Dagger component to get SpeakEasy. */ +/** Dagger component to get SpeakEasyCallManager. */ @Subcomponent public abstract class SpeakEasyComponent { - public abstract SpeakEasy speakEasy(); + public abstract SpeakEasyCallManager speakEasyCallManager(); public static SpeakEasyComponent get(Context context) { return ((SpeakEasyComponent.HasComponent) diff --git a/java/com/android/incallui/speakeasy/SpeakEasyStub.java b/java/com/android/incallui/speakeasy/SpeakEasyStub.java deleted file mode 100644 index 8b6b562ad..000000000 --- a/java/com/android/incallui/speakeasy/SpeakEasyStub.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.incallui.speakeasy; - -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import javax.inject.Inject; - -/** Default implementation of SpeakEasy. */ -public class SpeakEasyStub implements SpeakEasy { - - @Inject - public SpeakEasyStub() {} - - @Override - public boolean isEnabled() { - return false; - } - - @Override - public @Nullable Fragment getSpeakEasyFragment( - String callId, String nameOrNumber, long sessionStartTimeMillis) { - return null; - } -} diff --git a/java/com/android/incallui/speakeasy/StubSpeakEasyModule.java b/java/com/android/incallui/speakeasy/StubSpeakEasyModule.java index 9a6f47677..713ce2b44 100644 --- a/java/com/android/incallui/speakeasy/StubSpeakEasyModule.java +++ b/java/com/android/incallui/speakeasy/StubSpeakEasyModule.java @@ -19,10 +19,10 @@ package com.android.incallui.speakeasy; import dagger.Binds; import dagger.Module; -/** Module which binds {@link SpeakEasyStub}. */ +/** Module which binds {@link SpeakEasyCallManagerStub}. */ @Module public abstract class StubSpeakEasyModule { @Binds - abstract SpeakEasy bindsSpeakEasy(SpeakEasyStub stub); + abstract SpeakEasyCallManager bindsSpeakEasy(SpeakEasyCallManagerStub stub); } -- cgit v1.2.3