diff options
author | Sandeep Gutta <sangutta@codeaurora.org> | 2013-11-11 19:23:13 +0530 |
---|---|---|
committer | Sandeep Gutta <sangutta@codeaurora.org> | 2013-11-25 14:57:09 +0530 |
commit | d6e8ea435025aceadbf58ba78c4a2dd7fc745027 (patch) | |
tree | f33e72b81e5951dae3aa4a4679705b6892b4bed3 | |
parent | f053a386531bb464cd95e7069f2bd0c058792941 (diff) | |
download | android_packages_apps_InCallUI-d6e8ea435025aceadbf58ba78c4a2dd7fc745027.tar.gz android_packages_apps_InCallUI-d6e8ea435025aceadbf58ba78c4a2dd7fc745027.tar.bz2 android_packages_apps_InCallUI-d6e8ea435025aceadbf58ba78c4a2dd7fc745027.zip |
DSDA: Add InCallUI DSDA support.
-Add tab view support for DSDA
-Add support to display voice calls based on the
current active subscription.
-Add few utilities in CallList, which required for handling
voice calls across multiple subscription.
Change-Id: Ib683f7c3b41ed3bb04367be1e9c331908bc46004
22 files changed, 1102 insertions, 15 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9a873012..ee3c9de0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -49,6 +49,16 @@ android:exported="false"> </activity> + <activity android:name=".MSimInCallActivity" + android:theme="@style/Theme.InCallScreen" + android:label="@string/inCallLabel" + android:excludeFromRecents="true" + android:launchMode="singleInstance" + android:screenOrientation="nosensor" + android:configChanges="keyboardHidden" + android:exported="false"> + </activity> + <service android:name="CallHandlerService"> <intent-filter> <action android:name="com.android.services.telephony.common.ICallHandlerService" /> diff --git a/res/drawable-hdpi/ic_sim_icon_1.png b/res/drawable-hdpi/ic_sim_icon_1.png Binary files differnew file mode 100644 index 00000000..b95ab921 --- /dev/null +++ b/res/drawable-hdpi/ic_sim_icon_1.png diff --git a/res/drawable-hdpi/ic_sim_icon_2.png b/res/drawable-hdpi/ic_sim_icon_2.png Binary files differnew file mode 100644 index 00000000..a1725b2d --- /dev/null +++ b/res/drawable-hdpi/ic_sim_icon_2.png diff --git a/res/drawable-mdpi/ic_sim_icon_1.png b/res/drawable-mdpi/ic_sim_icon_1.png Binary files differnew file mode 100644 index 00000000..aa07a460 --- /dev/null +++ b/res/drawable-mdpi/ic_sim_icon_1.png diff --git a/res/drawable-mdpi/ic_sim_icon_2.png b/res/drawable-mdpi/ic_sim_icon_2.png Binary files differnew file mode 100644 index 00000000..581e0ae4 --- /dev/null +++ b/res/drawable-mdpi/ic_sim_icon_2.png diff --git a/res/drawable-xhdpi/ic_sim_icon_1.png b/res/drawable-xhdpi/ic_sim_icon_1.png Binary files differnew file mode 100644 index 00000000..05a798d9 --- /dev/null +++ b/res/drawable-xhdpi/ic_sim_icon_1.png diff --git a/res/drawable-xhdpi/ic_sim_icon_2.png b/res/drawable-xhdpi/ic_sim_icon_2.png Binary files differnew file mode 100644 index 00000000..700b75cd --- /dev/null +++ b/res/drawable-xhdpi/ic_sim_icon_2.png diff --git a/res/layout/incall_screen_msim.xml b/res/layout/incall_screen_msim.xml new file mode 100644 index 00000000..52f79a9f --- /dev/null +++ b/res/layout/incall_screen_msim.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) 2013 The Linux Foundation. All rights reserved. + Not a Contribution. + + Copyright (C) 2007 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. +--> + +<!-- In-call Phone UI; see InCallActivity.java. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/main"> + + <LinearLayout + android:id="@+id/in_call_and_button_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/in_call_card_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + + <fragment + android:name="com.android.incallui.CallCardFragment" + android:id="@+id/callCardFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" /> + <fragment + android:name="com.android.incallui.DialpadFragment" + android:id="@+id/dialpadFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" /> + </RelativeLayout> + + <fragment android:name="com.android.incallui.CallButtonFragment" + android:id="@+id/callButtonFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </LinearLayout> + + <fragment android:name="com.android.incallui.MSimAnswerFragment" + android:id="@+id/answerFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:gravity="top" + android:layout_gravity="bottom|center_horizontal" + android:layout_marginBottom="@dimen/glowpadview_margin_bottom" + android:visibility="gone" /> + + <fragment android:name="com.android.incallui.ConferenceManagerFragment" + android:id="@+id/conferenceManagerFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" /> + +</FrameLayout> diff --git a/res/layout/msim_tab_sub_info.xml b/res/layout/msim_tab_sub_info.xml new file mode 100644 index 00000000..2e71a19f --- /dev/null +++ b/res/layout/msim_tab_sub_info.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2013, The Linux Foundation. All rights reserved. + Not a Contribution. + + Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tab_sub_info" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal" > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center" + android:paddingTop="@dimen/dsda_sub_icon_marginTop" + android:orientation="vertical" > + + <ImageView + android:id="@+id/tabSubIcon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" + android:scaleType="centerCrop" /> + </LinearLayout> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:paddingTop="@dimen/dsda_sub_text_marginTop" + android:paddingLeft="@dimen/call_banner_side_padding"> + + <TextView + android:id="@+id/tabSubText" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" /> + </LinearLayout> + +</LinearLayout> diff --git a/res/values/array.xml b/res/values/array.xml index ba7e4cb3..50d28236 100644 --- a/res/values/array.xml +++ b/res/values/array.xml @@ -143,4 +143,10 @@ <item>@string/description_direction_down_right</item> </array> + <array name="sim_icons"> + <item>@drawable/ic_sim_icon_1</item> + <item>@drawable/ic_sim_icon_2</item> + <item>@null</item> + </array> + </resources> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d0b86a96..6a210d31 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -61,6 +61,9 @@ the prox sensor kick in.) --> <dimen name="button_cluster_side_padding">20dp</dimen> + <!-- Dimensions for DSDA switch tab --> + <dimen name="dsda_sub_icon_marginTop">14dp</dimen> + <dimen name="dsda_sub_text_marginTop">12dp</dimen> <!-- Dimensions for OTA Call Card --> <dimen name="otaactivate_layout_marginTop">10dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index e4c06860..83850a56 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1530,4 +1530,9 @@ <!-- Label for "Manage conference call" panel [CHAR LIMIT=40] --> <string name="manageConferenceLabel">Manage conference call</string> + + <!-- Set Subscription screen: label sub 1 --> + <string name="sub_1">SUB 1</string> + <!-- Set Subscription screen: label sub 2 --> + <string name="sub_2">SUB 2</string> </resources> diff --git a/res/values/styles.xml b/res/values/styles.xml index 45706532..adcfc5e9 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -126,7 +126,7 @@ window background instead of the default dark grey. (We don't just use Theme.Black.NoTitleBar directly, since we want any popups or dialogs from the InCallActivity to have the correct holo style. --> - <style name="Theme.InCallScreen" parent="@android:style/Theme.Holo.NoActionBar"> + <style name="Theme.InCallScreen" parent="@android:style/Theme.Holo"> <item name="android:windowBackground">@android:color/black</item> <item name="*android:windowAnimationStyle">@style/InCallAnimationStyle</item> diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java index 1f0ee4d9..20049f76 100644 --- a/src/com/android/incallui/CallButtonPresenter.java +++ b/src/com/android/incallui/CallButtonPresenter.java @@ -35,7 +35,8 @@ import android.telephony.PhoneNumberUtils; * Logic for call buttons. */ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi> - implements InCallStateListener, AudioModeListener, IncomingCallListener { + implements InCallStateListener, AudioModeListener, IncomingCallListener, + CallList.ActiveSubChangeListener { private Call mCall; private boolean mAutomaticallyMuted = false; @@ -58,6 +59,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto // register for call state changes last InCallPresenter.getInstance().addListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); + CallList.getInstance().addActiveSubChangeListener(this); } @Override @@ -67,6 +69,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto InCallPresenter.getInstance().removeListener(this); AudioModeProvider.getInstance().removeListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); + CallList.getInstance().removeActiveSubChangeListener(this); } @Override @@ -376,4 +379,12 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto void enableModifyCall(boolean enabled); void showModifyCall(boolean show); } + + @Override + public void onActiveSubChanged(int subscription) { + InCallState state = InCallPresenter.getInstance() + .getPotentialStateFromCallList(CallList.getInstance()); + + onStateChange(state, CallList.getInstance()); + } } diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java index 077538a4..3fb7ff0c 100644 --- a/src/com/android/incallui/CallCardFragment.java +++ b/src/com/android/incallui/CallCardFragment.java @@ -227,7 +227,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr showInternetCallLabel(isSipCall); - if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) { + if (MSimTelephonyManager.getDefault().isMultiSimEnabled() && + !(MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA)) { String[] sub = {"SUB 1", "SUB 2", "SUB 3"}; int subscription = getPresenter().getActiveSubscription(); diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java index a4825c6d..237e1234 100644 --- a/src/com/android/incallui/CallHandlerService.java +++ b/src/com/android/incallui/CallHandlerService.java @@ -51,7 +51,7 @@ public class CallHandlerService extends Service { private static final int ON_ACTIVE_SUB_CHANGE = 11; private static final int ON_UNSOL_CALLMODIFY = 12; - private static final int LARGEST_MSG_ID = ON_DESTROY; + private static final int LARGEST_MSG_ID = ON_ACTIVE_SUB_CHANGE; private CallList mCallList; diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java index a6aef7e5..5decb9da 100644 --- a/src/com/android/incallui/CallList.java +++ b/src/com/android/incallui/CallList.java @@ -24,6 +24,8 @@ import com.google.common.base.Preconditions; import android.os.Handler; import android.os.Message; +import android.telephony.MSimTelephonyManager; +import com.android.internal.telephony.MSimConstants; import com.android.services.telephony.common.Call; import java.util.ArrayList; @@ -43,6 +45,7 @@ public class CallList { private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000; private static final int EVENT_DISCONNECTED_TIMEOUT = 1; + private static final int EVENT_NOTIFY_CHANGE = 2; private static CallList sInstance = new CallList(); @@ -54,6 +57,8 @@ public class CallList { .newHashMap(); private int mSubscription = 0; + private final ArrayList<ActiveSubChangeListener> mActiveSubChangeListeners = + Lists.newArrayList(); /** * Static singleton accessor method. @@ -74,6 +79,8 @@ public class CallList { public void onUpdate(Call call) { Log.d(this, "onUpdate - ", call); + updateActiveSuscription(); + updateCallInMap(call); notifyListenersOfChange(); } @@ -101,6 +108,8 @@ public class CallList { public void onIncoming(Call call, List<String> textMessages) { Log.d(this, "onIncoming - " + call); + updateActiveSuscription(); + updateCallInMap(call); updateCallTextMap(call, textMessages); @@ -115,6 +124,8 @@ public class CallList { public void onUpdate(List<Call> callsToUpdate) { Log.d(this, "onUpdate(...)"); + updateActiveSuscription(); + Preconditions.checkNotNull(callsToUpdate); for (Call call : callsToUpdate) { Log.d(this, "\t" + call); @@ -283,6 +294,11 @@ public class CallList { * TODO: Improve this logic to sort by call time. */ public Call getCallWithState(int state, int positionToFind) { + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return getCallWithState(state, positionToFind, getActiveSubscription()); + } + Call retval = null; int position = 0; for (Call call : mCallMap.values()) { @@ -435,6 +451,13 @@ public class CallList { Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj); finishDisconnectedCall((Call) msg.obj); break; + case EVENT_NOTIFY_CHANGE: + Log.d(this, "EVENT_NOTIFY_CHANGE: "); + notifyListenersOfChange(); + for (ActiveSubChangeListener listener : mActiveSubChangeListeners) { + listener.onActiveSubChanged(getActiveSubscription()); + } + break; default: Log.wtf(this, "Message not expected: " + msg.what); break; @@ -480,12 +503,103 @@ public class CallList { * Called when active subscription changes. */ public void onActiveSubChanged(int activeSub) { - Log.d(this, "onActiveSubChanged: old = " + mSubscription + " new = " + activeSub); - - mSubscription = activeSub; + Log.d(this, "onActiveSubChanged = " + activeSub); + if (existsLiveCall(activeSub)) { + setActiveSubscription(activeSub); + } } public int getActiveSubscription() { return mSubscription; } + + /** + * Called to update the latest active subscription id, and also it + * notifies the registred clients about subscription change information. + */ + public void setActiveSubscription(int subscription) { + if (subscription != mSubscription) { + Log.i(this, "setActiveSubscription, old = " + mSubscription + " new = " + subscription); + mSubscription = subscription; + final Message msg = mHandler.obtainMessage(EVENT_NOTIFY_CHANGE, null); + mHandler.sendMessage(msg); + } + } + + /** + * Returns true, if any voice call in ACTIVE on the provided subscription. + */ + public boolean existsLiveCall(int subscription) { + for (Call call : mCallMap.values()) { + if (!isCallDead(call) && (call.getSubscription() == subscription)) { + return true; + } + } + return false; + } + + /** + * This method checks whether any other subscription currently has active voice + * call other than current active subscription, if yes it makes that other + * subscription as active subscription i.e user visible subscription. + */ + public boolean switchToOtherActiveSubscription() { + int activeSub = getActiveSubscription(); + boolean subSwitched = false; + + for (int i = 0; i < MSimTelephonyManager.getDefault().getPhoneCount(); i++) { + if ((i != activeSub) && existsLiveCall(i)) { + Log.i(this, "switchToOtherActiveSubscription, sub = " + i); + subSwitched = true; + setActiveSubscription(i); + break; + } + } + return subSwitched; + } + + /** + * Its a utility, gets the current active subscription from TeleService and + * updates the mSubscription member variable. + */ + public void updateActiveSuscription() { + if (!MSimTelephonyManager.getDefault().isMultiSimEnabled()) { + return; + } + setActiveSubscription(CallCommandClient.getInstance().getActiveSubscription()); + } + + /** + * Returns the [position]th call which belongs to provided subscription and + * found in the call map with the specified state. + */ + public Call getCallWithState(int state, int positionToFind, int subscription) { + Call retval = null; + int position = 0; + for (Call call : mCallMap.values()) { + if ((call.getState() == state) && (call.getSubscription() == subscription)) { + if (position >= positionToFind) { + retval = call; + break; + } else { + position++; + } + } + } + return retval; + } + + public void addActiveSubChangeListener(ActiveSubChangeListener listener) { + Preconditions.checkNotNull(listener); + mActiveSubChangeListeners.add(listener); + } + + public void removeActiveSubChangeListener(ActiveSubChangeListener listener) { + Preconditions.checkNotNull(listener); + mActiveSubChangeListeners.remove(listener); + } + + public interface ActiveSubChangeListener { + public void onActiveSubChanged(int subscription); + } } diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java index 92343d3e..80692f32 100644 --- a/src/com/android/incallui/InCallActivity.java +++ b/src/com/android/incallui/InCallActivity.java @@ -36,6 +36,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; +import android.telephony.MSimTelephonyManager; import android.view.KeyEvent; import android.view.View; import android.view.Window; @@ -52,13 +53,13 @@ public class InCallActivity extends Activity { private static final int INVALID_RES_ID = -1; - private CallButtonFragment mCallButtonFragment; - private CallCardFragment mCallCardFragment; + protected CallButtonFragment mCallButtonFragment; + protected CallCardFragment mCallCardFragment; private AnswerFragment mAnswerFragment; - private DialpadFragment mDialpadFragment; - private ConferenceManagerFragment mConferenceManagerFragment; + protected DialpadFragment mDialpadFragment; + protected ConferenceManagerFragment mConferenceManagerFragment; private boolean mIsForegroundActivity; - private AlertDialog mDialog; + protected AlertDialog mDialog; private AlertDialog mModifyCallPromptDialog; /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */ @@ -70,6 +71,11 @@ public class InCallActivity extends Activity { super.onCreate(icicle); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return; + } + // set this flag so this activity will stay in front of the keyguard // Have the WindowManager filter out touch events that are "too fat". getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED @@ -94,6 +100,11 @@ public class InCallActivity extends Activity { Log.d(this, "onStart()..."); super.onStart(); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + return; + } + // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(this); } @@ -148,7 +159,7 @@ public class InCallActivity extends Activity { return mIsForegroundActivity; } - private boolean hasPendingErrorDialog() { + protected boolean hasPendingErrorDialog() { return mDialog != null; } /** @@ -168,6 +179,11 @@ public class InCallActivity extends Activity { */ @Override public void finish() { + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + super.finish(); + return; + } Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); // skip finish if we are still showing a dialog. @@ -347,7 +363,7 @@ public class InCallActivity extends Activity { } } - private void initializeInCall() { + protected void initializeInCall() { if (mCallButtonFragment == null) { mCallButtonFragment = (CallButtonFragment) getFragmentManager() .findFragmentById(R.id.callButtonFragment); @@ -661,4 +677,7 @@ public class InCallActivity extends Activity { Log.e(this, msg); } + public void updateDsdaTab() { + Log.e(this, "updateDsdaTab : Not supported "); + } } diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java index 8ba186fa..d17e3e47 100644 --- a/src/com/android/incallui/InCallPresenter.java +++ b/src/com/android/incallui/InCallPresenter.java @@ -20,6 +20,8 @@ package com.android.incallui; +import android.telephony.MSimTelephonyManager; + import com.android.incallui.service.PhoneNumberService; import com.google.android.collect.Sets; import com.google.common.base.Preconditions; @@ -159,6 +161,11 @@ public class InCallPresenter implements CallList.Listener { final boolean doFinish = (mInCallActivity != null && isActivityStarted()); Log.i(this, "Hide in call UI: " + doFinish); + if ((mCallList != null) && !(mCallList.existsLiveCall(mCallList.getActiveSubscription())) + && mCallList.switchToOtherActiveSubscription()) { + return; + } + if (doFinish) { mInCallActivity.finish(); } @@ -329,6 +336,11 @@ public class InCallPresenter implements CallList.Listener { Log.d(this, "Notify " + listener + " of state " + mInCallState.toString()); listener.onStateChange(mInCallState, callList); } + + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA && (mInCallActivity != null)) { + mInCallActivity.updateDsdaTab(); + } } /** @@ -353,6 +365,11 @@ public class InCallPresenter implements CallList.Listener { for (IncomingCallListener listener : mIncomingCallListeners) { listener.onIncomingCall(mInCallState, call); } + + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA && (mInCallActivity != null)) { + mInCallActivity.updateDsdaTab(); + } } /** @@ -778,7 +795,12 @@ public class InCallPresenter implements CallList.Listener { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - intent.setClass(mContext, InCallActivity.class); + if (MSimTelephonyManager.getDefault().getMultiSimConfiguration() + == MSimTelephonyManager.MultiSimVariants.DSDA) { + intent.setClass(mContext, MSimInCallActivity.class); + } else { + intent.setClass(mContext, InCallActivity.class); + } if (showDialpad) { intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true); } diff --git a/src/com/android/incallui/msim/MSimAnswerFragment.java b/src/com/android/incallui/msim/MSimAnswerFragment.java new file mode 100644 index 00000000..92ff5c2c --- /dev/null +++ b/src/com/android/incallui/msim/MSimAnswerFragment.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Copyright (C) 2013 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; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; + +import com.google.common.base.Preconditions; + +import java.util.ArrayList; + +/** + * + */ +public class MSimAnswerFragment extends BaseFragment<MSimAnswerPresenter, + MSimAnswerPresenter.AnswerUi> + implements GlowPadWrapper.AnswerListener, MSimAnswerPresenter.AnswerUi { + + /** + * The popup showing the list of canned responses. + * + * This is an AlertDialog containing a ListView showing the possible choices. This may be null + * if the InCallScreen hasn't ever called showRespondViaSmsPopup() yet, or if the popup was + * visible once but then got dismissed. + */ + private Dialog mCannedResponsePopup = null; + + /** + * The popup showing a text field for users to type in their custom message. + */ + private AlertDialog mCustomMessagePopup = null; + + private ArrayAdapter<String> mTextResponsesAdapter = null; + + private GlowPadWrapper mGlowpad; + + public MSimAnswerFragment() { + } + + @Override + public MSimAnswerPresenter createPresenter() { + return new MSimAnswerPresenter(); + } + + @Override + MSimAnswerPresenter.AnswerUi getUi() { + return this; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mGlowpad = (GlowPadWrapper) inflater.inflate(R.layout.answer_fragment, + container, false); + + Log.d(this, "Creating view for answer fragment ", this); + Log.d(this, "Created from activity", getActivity()); + mGlowpad.setAnswerListener(this); + + return mGlowpad; + } + + @Override + public void onDestroyView() { + Log.d(this, "onDestroyView"); + if (mGlowpad != null) { + mGlowpad.stopPing(); + mGlowpad = null; + } + super.onDestroyView(); + } + + @Override + public void showAnswerUi(boolean show) { + getView().setVisibility(show ? View.VISIBLE : View.GONE); + + Log.d(this, "Show answer UI: " + show); + if (show) { + mGlowpad.startPing(); + } else { + mGlowpad.stopPing(); + } + } + + @Override + public void showTextButton(boolean show) { + final int targetResourceId = show + ? R.array.incoming_call_widget_3way_targets + : R.array.incoming_call_widget_2way_targets; + + if (targetResourceId != mGlowpad.getTargetResourceId()) { + if (show) { + // Answer, Decline, and Respond via SMS. + mGlowpad.setTargetResources(targetResourceId); + mGlowpad.setTargetDescriptionsResourceId( + R.array.incoming_call_widget_3way_target_descriptions); + mGlowpad.setDirectionDescriptionsResourceId( + R.array.incoming_call_widget_3way_direction_descriptions); + } else { + // Answer or Decline. + mGlowpad.setTargetResources(targetResourceId); + mGlowpad.setTargetDescriptionsResourceId( + R.array.incoming_call_widget_2way_target_descriptions); + mGlowpad.setDirectionDescriptionsResourceId( + R.array.incoming_call_widget_2way_direction_descriptions); + } + + mGlowpad.reset(false); + } + } + + @Override + public void showMessageDialog() { + final ListView lv = new ListView(getActivity()); + + Preconditions.checkNotNull(mTextResponsesAdapter); + lv.setAdapter(mTextResponsesAdapter); + lv.setOnItemClickListener(new RespondViaSmsItemClickListener()); + + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()).setCancelable( + true).setView(lv); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + if (mGlowpad != null) { + mGlowpad.startPing(); + } + } + }); + mCannedResponsePopup = builder.create(); + mCannedResponsePopup.show(); + } + + private boolean isCannedResponsePopupShowing() { + if (mCannedResponsePopup != null) { + return mCannedResponsePopup.isShowing(); + } + return false; + } + + private boolean isCustomMessagePopupShowing() { + if (mCustomMessagePopup != null) { + return mCustomMessagePopup.isShowing(); + } + return false; + } + + /** + * Dismiss the canned response list popup. + * + * This is safe to call even if the popup is already dismissed, and even if you never called + * showRespondViaSmsPopup() in the first place. + */ + private void dismissCannedResponsePopup() { + if (mCannedResponsePopup != null) { + mCannedResponsePopup.dismiss(); // safe even if already dismissed + mCannedResponsePopup = null; + } + } + + /** + * Dismiss the custom compose message popup. + */ + private void dismissCustomMessagePopup() { + if (mCustomMessagePopup != null) { + mCustomMessagePopup.dismiss(); + mCustomMessagePopup = null; + } + } + + public void dismissPendingDialogues() { + if (isCannedResponsePopupShowing()) { + dismissCannedResponsePopup(); + } + + if (isCustomMessagePopupShowing()) { + dismissCustomMessagePopup(); + } + } + + public boolean hasPendingDialogs() { + return !(mCannedResponsePopup == null && mCustomMessagePopup == null); + } + + /** + * Shows the custom message entry dialog. + */ + public void showCustomMessageDialog() { + // Create an alert dialog containing an EditText + final EditText et = new EditText(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()).setCancelable( + true).setView(et) + .setPositiveButton(R.string.custom_message_send, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // The order is arranged in a way that the popup will be destroyed when the + // InCallActivity is about to finish. + final String textMessage = et.getText().toString().trim(); + dismissCustomMessagePopup(); + getPresenter().rejectCallWithMessage(textMessage); + } + }) + .setNegativeButton(R.string.custom_message_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismissCustomMessagePopup(); + getPresenter().onDismissDialog(); + } + }) + .setTitle(R.string.respond_via_sms_custom_message); + mCustomMessagePopup = builder.create(); + + // Enable/disable the send button based on whether there is a message in the EditText + et.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + final Button sendButton = mCustomMessagePopup.getButton( + DialogInterface.BUTTON_POSITIVE); + sendButton.setEnabled(s != null && s.toString().trim().length() != 0); + } + }); + + // Keyboard up, show the dialog + mCustomMessagePopup.getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mCustomMessagePopup.show(); + + // Send button starts out disabled + final Button sendButton = mCustomMessagePopup.getButton(DialogInterface.BUTTON_POSITIVE); + sendButton.setEnabled(false); + } + + @Override + public void configureMessageDialog(ArrayList<String> textResponses) { + final ArrayList<String> textResponsesForDisplay = new ArrayList<String>(textResponses); + + textResponsesForDisplay.add(getResources().getString( + R.string.respond_via_sms_custom_message)); + mTextResponsesAdapter = new ArrayAdapter<String>(getActivity(), + android.R.layout.simple_list_item_1, android.R.id.text1, textResponsesForDisplay); + } + + @Override + public void onAnswer(int callType) { + getPresenter().onAnswer(callType); + } + + @Override + public void onDecline() { + getPresenter().onDecline(); + } + + @Override + public void onText() { + getPresenter().onText(); + } + + /** + * OnItemClickListener for the "Respond via SMS" popup. + */ + public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener { + + /** + * Handles the user selecting an item from the popup. + */ + @Override + public void onItemClick(AdapterView<?> parent, // The ListView + View view, // The TextView that was clicked + int position, long id) { + Log.d(this, "RespondViaSmsItemClickListener.onItemClick(" + position + ")..."); + final String message = (String) parent.getItemAtPosition(position); + Log.v(this, "- message: '" + message + "'"); + dismissCannedResponsePopup(); + + // The "Custom" choice is a special case. + // (For now, it's guaranteed to be the last item.) + if (position == (parent.getCount() - 1)) { + // Show the custom message dialog + showCustomMessageDialog(); + } else { + getPresenter().rejectCallWithMessage(message); + } + } + } +} diff --git a/src/com/android/incallui/msim/MSimAnswerPresenter.java b/src/com/android/incallui/msim/MSimAnswerPresenter.java new file mode 100644 index 00000000..375b05de --- /dev/null +++ b/src/com/android/incallui/msim/MSimAnswerPresenter.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Copyright (C) 2013 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; + +import android.telephony.MSimTelephonyManager; +import com.android.services.telephony.common.Call; + +import java.util.ArrayList; + +/** + * Presenter for the Incoming call widget. + */ +public class MSimAnswerPresenter extends Presenter<MSimAnswerPresenter.AnswerUi> + implements CallList.CallUpdateListener, CallList.Listener, + CallList.ActiveSubChangeListener { + + private static final String TAG = MSimAnswerPresenter.class.getSimpleName(); + + private int mCallId[] = {Call.INVALID_CALL_ID, Call.INVALID_CALL_ID}; + private Call mCall[] = {null, null}; + + @Override + public void onUiReady(AnswerUi ui) { + super.onUiReady(ui); + + final CallList calls = CallList.getInstance(); + final Call call = calls.getIncomingCall(); + // TODO: change so that answer presenter never starts up if it's not incoming. + if (call != null) { + processIncomingCall(call); + } + + // Listen for incoming calls. + calls.addListener(this); + CallList.getInstance().addActiveSubChangeListener(this); + } + + @Override + public void onUiUnready(AnswerUi ui) { + super.onUiUnready(ui); + + int subscription = CallList.getInstance().getActiveSubscription(); + CallList.getInstance().removeListener(this); + + // This is necessary because the activity can be destroyed while an incoming call exists. + // This happens when back button is pressed while incoming call is still being shown. + if (mCallId[subscription] != Call.INVALID_CALL_ID) { + CallList.getInstance().removeCallUpdateListener(mCallId[subscription], this); + } + CallList.getInstance().removeActiveSubChangeListener(this); + } + + @Override + public void onCallListChange(CallList callList) { + // no-op + } + + @Override + public void onDisconnect(Call call) { + // no-op + } + + @Override + public void onIncomingCall(Call call) { + int subscription = call.getSubscription(); + // TODO: Ui is being destroyed when the fragment detaches. Need clean up step to stop + // getting updates here. + Log.d(this, "onIncomingCall: " + this); + if (getUi() != null) { + if (call.getCallId() != mCallId[subscription]) { + // A new call is coming in. + processIncomingCall(call); + } + } + } + + private void processIncomingCall(Call call) { + int subscription = call.getSubscription(); + mCallId[subscription] = call.getCallId(); + mCall[subscription] = call; + + // Listen for call updates for the current call. + CallList.getInstance().addCallUpdateListener(mCallId[subscription], this); + + Log.d(TAG, "Showing incoming for call id: " + mCallId[subscription] + " " + this); + final ArrayList<String> textMsgs = CallList.getInstance().getTextResponses( + call.getCallId()); + getUi().showAnswerUi(true); + + if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { + getUi().showTextButton(true); + getUi().configureMessageDialog(textMsgs); + } else { + getUi().showTextButton(false); + } + } + + + @Override + public void onCallStateChanged(Call call) { + Log.d(this, "onCallStateChange() " + call + " " + this); + if (call.getState() != Call.State.INCOMING && call.getState() != Call.State.CALL_WAITING) { + int subscription = call.getSubscription(); + // Stop listening for updates. + CallList.getInstance().removeCallUpdateListener(mCallId[subscription], this); + + getUi().showAnswerUi(false); + + // mCallId will hold the state of the call. We don't clear the mCall variable here as + // it may be useful for sending text messages after phone disconnects. + mCallId[subscription] = Call.INVALID_CALL_ID; + } + } + + public void onAnswer(int callType) { + int subscription = CallList.getInstance().getActiveSubscription(); + if (mCallId[subscription] == Call.INVALID_CALL_ID) { + return; + } + + Log.d(this, "onAnswer " + mCallId[subscription]); + + CallCommandClient.getInstance().answerCall(mCallId[subscription]); + } + + public void onDecline() { + int subscription = CallList.getInstance().getActiveSubscription(); + Log.d(this, "onDecline " + mCallId[subscription]); + + CallCommandClient.getInstance().rejectCall(mCall[subscription], false, null); + } + + public void onText() { + if (getUi() != null) { + getUi().showMessageDialog(); + } + } + + public void rejectCallWithMessage(String message) { + int subscription = CallList.getInstance().getActiveSubscription(); + Log.d(this, "sendTextToDefaultActivity()..."); + + CallCommandClient.getInstance().rejectCall(mCall[subscription], true, message); + + onDismissDialog(); + } + + public void onDismissDialog() { + InCallPresenter.getInstance().onDismissDialog(); + } + + interface AnswerUi extends Ui { + public void showAnswerUi(boolean show); + public void showTextButton(boolean show); + public void showMessageDialog(); + public void configureMessageDialog(ArrayList<String> textResponses); + } + + @Override + public void onActiveSubChanged(int subscription) { + final CallList calls = CallList.getInstance(); + final Call call = calls.getIncomingCall(); + + if ((call != null) && (call.getCallId() == mCallId[subscription])) { + Log.i(TAG, "Show incoming for call id: " + mCallId[subscription] + " " + this); + final ArrayList<String> textMsgs = CallList.getInstance().getTextResponses( + call.getCallId()); + getUi().showAnswerUi(true); + + if (call.can(Call.Capabilities.RESPOND_VIA_TEXT) && textMsgs != null) { + getUi().showTextButton(true); + getUi().configureMessageDialog(textMsgs); + } else { + getUi().showTextButton(false); + } + } else if ((call == null) && (calls.existsLiveCall(subscription))) { + Log.i(TAG, "Hide incoming for call id: " + mCallId[subscription] + " " + this); + getUi().showAnswerUi(false); + } + } +} diff --git a/src/com/android/incallui/msim/MSimInCallActivity.java b/src/com/android/incallui/msim/MSimInCallActivity.java new file mode 100644 index 00000000..3f61a595 --- /dev/null +++ b/src/com/android/incallui/msim/MSimInCallActivity.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2013 The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Copyright (C) 2006 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; + +import android.app.ActionBar; +import android.app.FragmentTransaction; +import android.app.ActionBar.Tab; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.telephony.MSimTelephonyManager; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * Phone app "multisim in call" screen. + */ +public class MSimInCallActivity extends InCallActivity { + + private MSimAnswerFragment mAnswerFragment; + + private final int TAB_COUNT_ONE = 1; + private final int TAB_COUNT_TWO = 2; + private final int TAB_POSITION_FIRST = 0; + + private Tab[] mDsdaTab = new Tab[TAB_COUNT_TWO]; + private boolean[] mDsdaTabAdd = {false, false}; + + @Override + protected void onCreate(Bundle icicle) { + Log.d(this, "onCreate()... this = " + this); + + super.onCreate(icicle); + + // set this flag so this activity will stay in front of the keyguard + // Have the WindowManager filter out touch events that are "too fat". + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES); + + requestWindowFeature(Window.FEATURE_ACTION_BAR); + + getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + getActionBar().setDisplayShowTitleEnabled(false); + getActionBar().setDisplayShowHomeEnabled(false); + + // Inflate everything in incall_screen.xml and add it to the screen. + setContentView(R.layout.incall_screen_msim); + + initializeInCall(); + + initializeDsdaSwitchTab(); + Log.d(this, "onCreate(): exit"); + } + + @Override + protected void onStart() { + Log.d(this, "onStart()..."); + super.onStart(); + + // setting activity should be last thing in setup process + InCallPresenter.getInstance().setActivity(this); + } + + @Override + public void finish() { + Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); + + // skip finish if we are still showing a dialog. + if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) { + super.finish(); + } + } + + @Override + protected void initializeInCall() { + if (mCallButtonFragment == null) { + mCallButtonFragment = (CallButtonFragment) getFragmentManager() + .findFragmentById(R.id.callButtonFragment); + mCallButtonFragment.getView().setVisibility(View.INVISIBLE); + } + + if (mCallCardFragment == null) { + mCallCardFragment = (CallCardFragment) getFragmentManager() + .findFragmentById(R.id.callCardFragment); + } + + if (mAnswerFragment == null) { + mAnswerFragment = (MSimAnswerFragment) getFragmentManager() + .findFragmentById(R.id.answerFragment); + } + + if (mDialpadFragment == null) { + mDialpadFragment = (DialpadFragment) getFragmentManager() + .findFragmentById(R.id.dialpadFragment); + mDialpadFragment.getView().setVisibility(View.INVISIBLE); + } + + if (mConferenceManagerFragment == null) { + mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager() + .findFragmentById(R.id.conferenceManagerFragment); + mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE); + } + } + + @Override + public void dismissPendingDialogs() { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + mAnswerFragment.dismissPendingDialogues(); + } + + private void initializeDsdaSwitchTab() { + int phoneCount = MSimTelephonyManager.getDefault().getPhoneCount(); + ActionBar bar = getActionBar(); + View[] mDsdaTabLayout = new View[phoneCount]; + TypedArray icons = getResources().obtainTypedArray(R.array.sim_icons); + int[] subString = {R.string.sub_1, R.string.sub_2}; + + for (int i = 0; i < phoneCount; i++) { + mDsdaTabLayout[i] = getLayoutInflater() + .inflate(R.layout.msim_tab_sub_info, null); + + ((ImageView)mDsdaTabLayout[i].findViewById(R.id.tabSubIcon)) + .setBackground(icons.getDrawable(i)); + + ((TextView)mDsdaTabLayout[i].findViewById(R.id.tabSubText)) + .setText(subString[i]); + + mDsdaTab[i] = bar.newTab().setCustomView(mDsdaTabLayout[i]) + .setTabListener(new TabListener(i)); + } + } + + @Override + public void updateDsdaTab() { + int phoneCount = MSimTelephonyManager.getDefault().getPhoneCount(); + ActionBar bar = getActionBar(); + + for (int i = 0; i < phoneCount; i++) { + if (CallList.getInstance().existsLiveCall(i)) { + if (!mDsdaTabAdd[i]) { + addDsdaTab(i); + } + } else { + removeDsdaTab(i); + } + } + + updateDsdaTabSelection(); + } + + private void addDsdaTab(int subscription) { + ActionBar bar = getActionBar(); + int tabCount = bar.getTabCount(); + + if (tabCount < subscription) { + bar.addTab(mDsdaTab[subscription], false); + } else { + bar.addTab(mDsdaTab[subscription], subscription, false); + } + mDsdaTabAdd[subscription] = true; + } + + private void removeDsdaTab(int subscription) { + ActionBar bar = getActionBar(); + int tabCount = bar.getTabCount(); + + for (int i = 0; i < tabCount; i++) { + if (bar.getTabAt(i).equals(mDsdaTab[subscription])) { + bar.removeTab(mDsdaTab[subscription]); + mDsdaTabAdd[subscription] = false; + return; + } + } + } + + private void updateDsdaTabSelection() { + ActionBar bar = getActionBar(); + int barCount = bar.getTabCount(); + + if (barCount == TAB_COUNT_ONE) { + bar.selectTab(bar.getTabAt(TAB_POSITION_FIRST)); + } else if (barCount == TAB_COUNT_TWO) { + bar.selectTab(bar.getTabAt(CallList.getInstance().getActiveSubscription())); + } + } + + private class TabListener implements ActionBar.TabListener { + int mSubscription; + + public TabListener(int subId) { + mSubscription = subId; + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + ActionBar bar = getActionBar(); + + if (CallList.getInstance().existsLiveCall(mSubscription)) { + CallCommandClient.getInstance().setActiveSubscription(mSubscription); + } + } + + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + } + + public void onTabReselected(Tab tab, FragmentTransaction ft) { + } + } +} |