summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanny Baumann <dannybaumann@web.de>2016-02-01 14:07:17 +0100
committerGerrit Code Review <gerrit@cyanogenmod.org>2016-02-10 04:19:16 -0800
commitdef25800f9a3acd5d4e8e063419ec95a139e0795 (patch)
tree61093495293019b93e12eced8f303f24fe66705f
parent0b8ca16a21f0656d9a10a9e413b601780f439469 (diff)
downloadpackages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.tar.gz
packages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.tar.bz2
packages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.zip
Re-add call recording feature.
Change-Id: I44f766b2ef52d76ace65a9603401ba3368f674b1
-rw-r--r--res/drawable-hdpi/ic_recording_indicator.pngbin0 -> 1305 bytes
-rw-r--r--res/drawable-hdpi/ic_toolbar_record.pngbin0 -> 410 bytes
-rw-r--r--res/drawable-mdpi/ic_recording_indicator.pngbin0 -> 1190 bytes
-rw-r--r--res/drawable-mdpi/ic_toolbar_record.pngbin0 -> 328 bytes
-rw-r--r--res/drawable-xhdpi/ic_recording_indicator.pngbin0 -> 1341 bytes
-rw-r--r--res/drawable-xhdpi/ic_toolbar_record.pngbin0 -> 500 bytes
-rw-r--r--res/drawable-xxhdpi/ic_toolbar_record.pngbin0 -> 799 bytes
-rw-r--r--res/drawable/btn_compound_record.xml32
-rw-r--r--res/layout/call_button_fragment.xml7
-rw-r--r--res/layout/call_card_fragment.xml29
-rw-r--r--res/values/cm_strings.xml8
-rw-r--r--src/com/android/incallui/Call.java5
-rw-r--r--src/com/android/incallui/CallButtonFragment.java48
-rw-r--r--src/com/android/incallui/CallButtonPresenter.java69
-rw-r--r--src/com/android/incallui/CallCardFragment.java46
-rw-r--r--src/com/android/incallui/CallList.java14
-rw-r--r--src/com/android/incallui/CallRecorder.java270
-rw-r--r--src/com/android/incallui/InCallServiceImpl.java1
-rw-r--r--src/com/android/incallui/Presenter.java10
19 files changed, 535 insertions, 4 deletions
diff --git a/res/drawable-hdpi/ic_recording_indicator.png b/res/drawable-hdpi/ic_recording_indicator.png
new file mode 100644
index 00000000..a98b837d
--- /dev/null
+++ b/res/drawable-hdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_toolbar_record.png b/res/drawable-hdpi/ic_toolbar_record.png
new file mode 100644
index 00000000..b395cdbd
--- /dev/null
+++ b/res/drawable-hdpi/ic_toolbar_record.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_recording_indicator.png b/res/drawable-mdpi/ic_recording_indicator.png
new file mode 100644
index 00000000..2a4c19ee
--- /dev/null
+++ b/res/drawable-mdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_toolbar_record.png b/res/drawable-mdpi/ic_toolbar_record.png
new file mode 100644
index 00000000..4a99405d
--- /dev/null
+++ b/res/drawable-mdpi/ic_toolbar_record.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_recording_indicator.png b/res/drawable-xhdpi/ic_recording_indicator.png
new file mode 100644
index 00000000..33e68751
--- /dev/null
+++ b/res/drawable-xhdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_toolbar_record.png b/res/drawable-xhdpi/ic_toolbar_record.png
new file mode 100644
index 00000000..b2fc680c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_toolbar_record.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_toolbar_record.png b/res/drawable-xxhdpi/ic_toolbar_record.png
new file mode 100644
index 00000000..b1fd09d1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_toolbar_record.png
Binary files differ
diff --git a/res/drawable/btn_compound_record.xml b/res/drawable/btn_compound_record.xml
new file mode 100644
index 00000000..40425253
--- /dev/null
+++ b/res/drawable/btn_compound_record.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Layers used to render the "call record" compound button. -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- The standard "compound button" background. -->
+ <item android:id="@+id/compoundBackgroundItem"
+ android:drawable="@drawable/btn_compound_background" />
+
+ <!-- ...and the actual icon on top. Use an explicit <bitmap> to avoid scaling
+ the icon up to the full size of the button. -->
+ <item>
+ <bitmap android:src="@drawable/ic_toolbar_record"
+ android:gravity="center"
+ android:tint="@color/selectable_icon_tint" />
+ </item>
+
+</layer-list>
diff --git a/res/layout/call_button_fragment.xml b/res/layout/call_button_fragment.xml
index 149e397d..e40d47e2 100644
--- a/res/layout/call_button_fragment.xml
+++ b/res/layout/call_button_fragment.xml
@@ -151,6 +151,13 @@
android:contentDescription="@string/onscreenAddParticipant"
android:visibility="gone" />
+ <!-- "Record call" -->
+ <ToggleButton android:id="@+id/callRecordButton"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_record"
+ android:contentDescription="@string/onscreenCallRecordText"
+ android:visibility="gone" />
+
<!-- "Overflow" -->
<ImageButton android:id="@+id/overflowButton"
style="@style/InCallButton"
diff --git a/res/layout/call_card_fragment.xml b/res/layout/call_card_fragment.xml
index dabba766..ed7031a4 100644
--- a/res/layout/call_card_fragment.xml
+++ b/res/layout/call_card_fragment.xml
@@ -78,6 +78,35 @@
android:background="@android:color/white"
android:src="@drawable/img_no_image_automirrored" />
+ <!-- Call recorder info -->
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|start"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="120dp"
+ android:orientation="horizontal">
+
+ <TextView android:id="@+id/recordingIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:drawableEnd="@drawable/ic_recording_indicator"
+ android:visibility="invisible" />
+
+ <TextView android:id="@+id/recordingTime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="5dp"
+ android:layout_gravity="center"
+ android:text="@string/recording_time_text"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/incall_call_banner_text_color"
+ android:singleLine="true"
+ android:visibility="invisible" />
+
+ </LinearLayout>
+
</FrameLayout>
<fragment android:name="com.android.incallui.VideoCallFragment"
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index cc9e0009..e486a4c5 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -35,4 +35,12 @@
<!-- In-call screen: status label for a call that is held remotely -->
<string name="card_title_waiting_call">Call on hold</string>
+
+ <string name="call_recording_failed_message">Failed to start call recording</string>
+ <string name="call_recording_file_location">Saved call recording to <xliff:g id="filename">%s</xliff:g></string>
+ <string name="onscreenCallRecordText">Record call</string>
+ <string name="onscreenStopCallRecordText">Stop recording</string>
+ <string name="recording_time_text">Recording</string>
+ <string name="recording_warning_title">Enable call recording?</string>
+ <string name="recording_warning_text">Notice: You are responsible for compliance with any laws, regulations and rules that apply to the use of call recording functionality and the use or distribution of those recordings.</string>
</resources>
diff --git a/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java
index f769f398..7f232c10 100644
--- a/src/com/android/incallui/Call.java
+++ b/src/com/android/incallui/Call.java
@@ -586,6 +586,11 @@ public class Call {
return mTelecommCall.getDetails().getConnectTimeMillis();
}
+ /** Gets the time when call was first constructed */
+ public long getCreateTimeMillis() {
+ return mTelecommCall.getDetails().getCreateTimeMillis();
+ }
+
public boolean isConferenceCall() {
return hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE);
}
diff --git a/src/com/android/incallui/CallButtonFragment.java b/src/com/android/incallui/CallButtonFragment.java
index 2b6d0f75..ff5329bd 100644
--- a/src/com/android/incallui/CallButtonFragment.java
+++ b/src/com/android/incallui/CallButtonFragment.java
@@ -18,7 +18,9 @@ package com.android.incallui;
import static com.android.incallui.CallButtonFragment.Buttons.*;
+import android.annotation.NonNull;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -60,6 +62,8 @@ public class CallButtonFragment
// The button has been collapsed into the overflow menu
private static final int BUTTON_MENU = 3;
+ private static final int REQUEST_CODE_CALL_RECORD_PERMISSION = 1000;
+
public interface Buttons {
public static final int BUTTON_AUDIO = 0;
public static final int BUTTON_MUTE = 1;
@@ -72,7 +76,8 @@ public class CallButtonFragment
public static final int BUTTON_MERGE = 8;
public static final int BUTTON_PAUSE_VIDEO = 9;
public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 10;
- public static final int BUTTON_COUNT = 11;
+ public static final int BUTTON_RECORD_CALL = 11;
+ public static final int BUTTON_COUNT = 12;
}
private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT);
@@ -87,6 +92,7 @@ public class CallButtonFragment
private ImageButton mAddCallButton;
private ImageButton mMergeButton;
private CompoundButton mPauseVideoButton;
+ private CompoundButton mCallRecordButton;
private ImageButton mOverflowButton;
private ImageButton mManageVideoCallConferenceButton;
private ImageButton mAddParticipantButton;
@@ -151,6 +157,8 @@ public class CallButtonFragment
mMergeButton.setOnClickListener(this);
mPauseVideoButton = (CompoundButton) parent.findViewById(R.id.pauseVideoButton);
mPauseVideoButton.setOnClickListener(this);
+ mCallRecordButton = (CompoundButton) parent.findViewById(R.id.callRecordButton);
+ mCallRecordButton.setOnClickListener(this);
mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant);
mAddParticipantButton.setOnClickListener(this);
mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
@@ -223,6 +231,9 @@ public class CallButtonFragment
getPresenter().pauseVideoClicked(
!mPauseVideoButton.isSelected() /* pause */);
break;
+ case R.id.callRecordButton:
+ getPresenter().callRecordClicked(!mCallRecordButton.isSelected());
+ break;
case R.id.overflowButton:
if (mOverflowPopup != null) {
mOverflowPopup.show();
@@ -254,7 +265,8 @@ public class CallButtonFragment
mShowDialpadButton,
mHoldButton,
mSwitchCameraButton,
- mPauseVideoButton
+ mPauseVideoButton,
+ mCallRecordButton
};
for (View button : compoundButtons) {
@@ -359,6 +371,7 @@ public class CallButtonFragment
mAddCallButton.setEnabled(isEnabled);
mMergeButton.setEnabled(isEnabled);
mPauseVideoButton.setEnabled(isEnabled);
+ mCallRecordButton.setEnabled(isEnabled);
mOverflowButton.setEnabled(isEnabled);
mManageVideoCallConferenceButton.setEnabled(isEnabled);
mAddParticipantButton.setEnabled(isEnabled);
@@ -401,6 +414,8 @@ public class CallButtonFragment
return mPauseVideoButton;
case BUTTON_MANAGE_VIDEO_CONFERENCE:
return mManageVideoCallConferenceButton;
+ case BUTTON_RECORD_CALL:
+ return mCallRecordButton;
default:
Log.w(this, "Invalid button id");
return null;
@@ -438,6 +453,14 @@ public class CallButtonFragment
}
}
+ @Override
+ public void setCallRecordingState(boolean isRecording) {
+ mCallRecordButton.setSelected(isRecording);
+ mCallRecordButton.setContentDescription(getContext().getString(isRecording
+ ? R.string.onscreenStopCallRecordText
+ : R.string.onscreenCallRecordText));
+ }
+
private void addToOverflowMenu(int id, View button, PopupMenu menu) {
button.setVisibility(View.GONE);
menu.getMenu().add(Menu.NONE, id, Menu.NONE, button.getContentDescription());
@@ -807,6 +830,27 @@ public class CallButtonFragment
}
@Override
+ public void requestCallRecordingPermission(String[] permissions) {
+ requestPermissions(permissions, REQUEST_CODE_CALL_RECORD_PERMISSION);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ if (requestCode == REQUEST_CODE_CALL_RECORD_PERMISSION) {
+ boolean allGranted = grantResults.length > 0;
+ for (int i = 0; i < grantResults.length; i++) {
+ allGranted &= grantResults[i] == PackageManager.PERMISSION_GRANTED;
+ }
+ if (allGranted) {
+ getPresenter().startCallRecording();
+ }
+ } else {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ @Override
public Context getContext() {
return getActivity();
}
diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java
index a309b566..ce3f27d4 100644
--- a/src/com/android/incallui/CallButtonPresenter.java
+++ b/src/com/android/incallui/CallButtonPresenter.java
@@ -18,7 +18,11 @@ package com.android.incallui;
import static com.android.incallui.CallButtonFragment.Buttons.*;
+import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.InCallService.VideoCall;
@@ -45,6 +49,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
+ private static final String RECORDING_WARNING_PRESENTED = "recording_warning_presented";
private Call mCall;
private boolean mAutomaticallyMuted = false;
@@ -343,6 +348,63 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
getUi().setVideoPaused(pause);
}
+ public void callRecordClicked(boolean startRecording) {
+ CallRecorder recorder = CallRecorder.getInstance();
+ if (startRecording) {
+ Context context = getUi().getContext();
+ final SharedPreferences prefs = getPrefs(context);
+ boolean warningPresented = prefs.getBoolean(RECORDING_WARNING_PRESENTED, false);
+ if (!warningPresented) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.recording_warning_title)
+ .setMessage(R.string.recording_warning_text)
+ .setPositiveButton(R.string.onscreenCallRecordText,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ prefs.edit()
+ .putBoolean(RECORDING_WARNING_PRESENTED, true)
+ .apply();
+ startCallRecordingOrAskForPermission();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ } else {
+ startCallRecordingOrAskForPermission();
+ }
+ } else {
+ if (recorder.isRecording()) {
+ recorder.finishRecording();
+ }
+ getUi().setCallRecordingState(recorder.isRecording());
+ }
+ }
+
+ public void startCallRecording() {
+ CallRecorder recorder = CallRecorder.getInstance();
+ recorder.startRecording(mCall.getNumber(), mCall.getCreateTimeMillis());
+ getUi().setCallRecordingState(recorder.isRecording());
+ }
+
+ private void startCallRecordingOrAskForPermission() {
+ if (hasAllPermissions(CallRecorder.REQUIRED_PERMISSIONS)) {
+ startCallRecording();
+ } else {
+ getUi().requestCallRecordingPermission(CallRecorder.REQUIRED_PERMISSIONS);
+ }
+ }
+
+ private boolean hasAllPermissions(String[] permissions) {
+ Context context = getUi().getContext();
+ for (String p : permissions) {
+ if (context.checkSelfPermission(p) != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void updateUi(InCallState state, Call call) {
Log.d(this, "Updating call UI for call: ", call);
@@ -398,6 +460,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
final boolean showAddParticipant = call.can(
android.telecom.Call.Details.CAPABILITY_ADD_PARTICIPANT);
+ final CallRecorder recorder = CallRecorder.getInstance();
+ boolean showCallRecordOption = recorder.isEnabled()
+ && !isVideo && call.getState() == Call.State.ACTIVE;
+
ui.showButton(BUTTON_AUDIO, true);
ui.showButton(BUTTON_SWAP, showSwap);
ui.showButton(BUTTON_HOLD, showHold);
@@ -409,6 +475,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
ui.showButton(BUTTON_PAUSE_VIDEO, isVideo && !useExt);
ui.showButton(BUTTON_DIALPAD, !isVideo || useExt);
ui.showButton(BUTTON_MERGE, showMerge);
+ ui.showButton(BUTTON_RECORD_CALL, showCallRecordOption);
ui.enableAddParticipant(showAddParticipant);
ui.updateButtonStates();
@@ -453,6 +520,8 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
void enableAddParticipant(boolean show);
void setAudio(int mode);
void setSupportedAudio(int mask);
+ void setCallRecordingState(boolean isRecording);
+ void requestCallRecordingPermission(String[] permissions);
void displayDialpad(boolean on, boolean animate);
boolean isDialpadVisible();
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index 57372f72..ecc26944 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.java
@@ -130,6 +130,8 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
// Container view that houses the primary call information
private ViewGroup mPrimaryCallInfo;
private View mCallButtonsContainer;
+ private TextView mRecordingTimeLabel;
+ private TextView mRecordingIcon;
// Secondary caller info
private View mSecondaryCallInfo;
@@ -171,6 +173,36 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
*/
private boolean mHasSecondaryCallInfo = false;
+ private CallRecorder.RecordingProgressListener mRecordingProgressListener =
+ new CallRecorder.RecordingProgressListener() {
+ @Override
+ public void onStartRecording() {
+ mRecordingTimeLabel.setText(DateUtils.formatElapsedTime(0));
+ if (mRecordingTimeLabel.getVisibility() != View.VISIBLE) {
+ AnimUtils.fadeIn(mRecordingTimeLabel, AnimUtils.DEFAULT_DURATION);
+ }
+ if (mRecordingIcon.getVisibility() != View.VISIBLE) {
+ AnimUtils.fadeIn(mRecordingIcon, AnimUtils.DEFAULT_DURATION);
+ }
+ }
+
+ @Override
+ public void onStopRecording() {
+ AnimUtils.fadeOut(mRecordingTimeLabel, AnimUtils.DEFAULT_DURATION);
+ AnimUtils.fadeOut(mRecordingIcon, AnimUtils.DEFAULT_DURATION);
+ }
+
+ @Override
+ public void onRecordingTimeProgress(final long elapsedTimeMs) {
+ long elapsedSeconds = (elapsedTimeMs + 500) / 1000;
+ mRecordingTimeLabel.setText(DateUtils.formatElapsedTime(elapsedSeconds));
+
+ // make sure this is visible in case we re-loaded the UI for a call in progress
+ mRecordingTimeLabel.setVisibility(View.VISIBLE);
+ mRecordingIcon.setVisibility(View.VISIBLE);
+ }
+ };
+
@Override
public CallCardPresenter.CallCardUi getUi() {
return this;
@@ -291,6 +323,20 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mPrimaryName.setElegantTextHeight(false);
mCallStateLabel.setElegantTextHeight(false);
mCallSubject = (TextView) view.findViewById(R.id.callSubject);
+
+ mRecordingTimeLabel = (TextView) view.findViewById(R.id.recordingTime);
+ mRecordingIcon = (TextView) view.findViewById(R.id.recordingIcon);
+
+ CallRecorder recorder = CallRecorder.getInstance();
+ recorder.addRecordingProgressListener(mRecordingProgressListener);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ CallRecorder recorder = CallRecorder.getInstance();
+ recorder.removeRecordingProgressListener(mRecordingProgressListener);
}
@Override
diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java
index 0b4f11a9..6ff5c991 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -21,13 +21,14 @@ import android.os.Message;
import android.os.Trace;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
import com.android.contacts.common.testing.NeededForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.SubscriptionManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -850,6 +851,15 @@ public class CallList {
return retval;
}
+ public Call getCallWithStateAndNumber(int state, String number) {
+ for (Call call : mCallById.values()) {
+ if (TextUtils.equals(call.getNumber(), number) && call.getState() == state) {
+ return call;
+ }
+ }
+ return null;
+ }
+
void addActiveSubChangeListener(ActiveSubChangeListener listener) {
Preconditions.checkNotNull(listener);
mActiveSubChangeListeners.add(listener);
diff --git a/src/com/android/incallui/CallRecorder.java b/src/com/android/incallui/CallRecorder.java
new file mode 100644
index 00000000..cf04d950
--- /dev/null
+++ b/src/com/android/incallui/CallRecorder.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.services.callrecorder.CallRecorderService;
+import com.android.services.callrecorder.CallRecordingDataStore;
+import com.android.services.callrecorder.common.CallRecording;
+import com.android.services.callrecorder.common.ICallRecorderService;
+
+import java.util.Date;
+import java.util.HashSet;
+
+/**
+ * InCall UI's interface to the call recorder
+ *
+ * Manages the call recorder service lifecycle. We bind to the service whenever an active call
+ * is established, and unbind when all calls have been disconnected.
+ */
+public class CallRecorder implements CallList.Listener {
+ public static final String TAG = "CallRecorder";
+
+ public static final String[] REQUIRED_PERMISSIONS = new String[] {
+ android.Manifest.permission.RECORD_AUDIO,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ };
+
+ private static CallRecorder sInstance = null;
+
+ private Context mContext;
+ private boolean mInitialized = false;
+ private ICallRecorderService mService = null;
+
+ private HashSet<RecordingProgressListener> mProgressListeners =
+ new HashSet<RecordingProgressListener>();
+ private Handler mHandler = new Handler();
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ICallRecorderService.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+ };
+
+ public static CallRecorder getInstance() {
+ if (sInstance == null) {
+ sInstance = new CallRecorder();
+ }
+ return sInstance;
+ }
+
+ public boolean isEnabled() {
+ return CallRecorderService.isEnabled(mContext);
+ }
+
+ private CallRecorder() {
+ CallList.getInstance().addListener(this);
+ }
+
+ public void setUp(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ private void initialize() {
+ if (isEnabled() && !mInitialized) {
+ Intent serviceIntent = new Intent(mContext, CallRecorderService.class);
+ mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
+ mInitialized = true;
+ }
+ }
+
+ private void uninitialize() {
+ if (mInitialized) {
+ mContext.unbindService(mConnection);
+ mInitialized = false;
+ }
+ }
+
+ public boolean startRecording(final String phoneNumber, final long creationTime) {
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ if (mService.startRecording(phoneNumber, creationTime)) {
+ for (RecordingProgressListener l : mProgressListeners) {
+ l.onStartRecording();
+ }
+ mUpdateRecordingProgressTask.run();
+ return true;
+ } else {
+ Toast.makeText(mContext, R.string.call_recording_failed_message,
+ Toast.LENGTH_SHORT).show();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to start recording " + phoneNumber + ", " +
+ new Date(creationTime), e);
+ }
+
+ return false;
+ }
+
+ public boolean isRecording() {
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ return mService.isRecording();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Exception checking recording status", e);
+ }
+ return false;
+ }
+
+ public CallRecording getActiveRecording() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getActiveRecording();
+ } catch (RemoteException e) {
+ Log.w("Exception getting active recording", e);
+ }
+ return null;
+ }
+
+ public void finishRecording() {
+ if (mService != null) {
+ try {
+ final CallRecording recording = mService.stopRecording();
+ if (recording != null) {
+ if (!TextUtils.isEmpty(recording.phoneNumber)) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ CallRecordingDataStore dataStore = new CallRecordingDataStore();
+ dataStore.open(mContext);
+ dataStore.putRecording(recording);
+ dataStore.close();
+ }
+ }).start();
+ } else {
+ // Data store is an index by number so that we can link recordings in the
+ // call detail page. If phone number is not available (conference call or
+ // unknown number) then just display a toast.
+ String msg = mContext.getResources().getString(
+ R.string.call_recording_file_location, recording.fileName);
+ Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to stop recording", e);
+ }
+ }
+
+ for (RecordingProgressListener l : mProgressListeners) {
+ l.onStopRecording();
+ }
+ mHandler.removeCallbacks(mUpdateRecordingProgressTask);
+ }
+
+ //
+ // Call list listener methods.
+ //
+ @Override
+ public void onIncomingCall(Call call) {
+ // do nothing
+ }
+
+ @Override
+ public void onCallListChange(final CallList callList) {
+ if (!mInitialized && callList.getActiveCall() != null) {
+ // we'll come here if this is the first active call
+ initialize();
+ } else {
+ // we can come down this branch to resume a call that was on hold
+ CallRecording active = getActiveRecording();
+ if (active != null) {
+ Call call = callList.getCallWithStateAndNumber(Call.State.ONHOLD,
+ active.phoneNumber);
+ if (call != null) {
+ // The call associated with the active recording has been placed
+ // on hold, so stop the recording.
+ finishRecording();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDisconnect(final Call call) {
+ CallRecording active = getActiveRecording();
+ if (active != null && TextUtils.equals(call.getNumber(), active.phoneNumber)) {
+ // finish the current recording if the call gets disconnected
+ finishRecording();
+ }
+
+ // tear down the service if there are no more active calls
+ if (CallList.getInstance().getActiveCall() == null) {
+ uninitialize();
+ }
+ }
+
+ @Override
+ public void onUpgradeToVideo(Call call) {}
+
+ // allow clients to listen for recording progress updates
+ public interface RecordingProgressListener {
+ public void onStartRecording();
+ public void onStopRecording();
+ public void onRecordingTimeProgress(long elapsedTimeMs);
+ }
+
+ public void addRecordingProgressListener(RecordingProgressListener listener) {
+ mProgressListeners.add(listener);
+ }
+
+ public void removeRecordingProgressListener(RecordingProgressListener listener) {
+ mProgressListeners.remove(listener);
+ }
+
+ private static final int UPDATE_INTERVAL = 500;
+
+ private Runnable mUpdateRecordingProgressTask = new Runnable() {
+ @Override
+ public void run() {
+ CallRecording active = getActiveRecording();
+ if (active != null) {
+ long elapsed = System.currentTimeMillis() - active.startRecordingTime;
+ for (RecordingProgressListener l : mProgressListeners) {
+ l.onRecordingTimeProgress(elapsed);
+ }
+ }
+ mHandler.postDelayed(mUpdateRecordingProgressTask, UPDATE_INTERVAL);
+ }
+ };
+}
diff --git a/src/com/android/incallui/InCallServiceImpl.java b/src/com/android/incallui/InCallServiceImpl.java
index 230a2cc1..b201e78c 100644
--- a/src/com/android/incallui/InCallServiceImpl.java
+++ b/src/com/android/incallui/InCallServiceImpl.java
@@ -82,6 +82,7 @@ public class InCallServiceImpl extends InCallService {
InCallPresenter.getInstance().onServiceBind();
InCallPresenter.getInstance().maybeStartRevealAnimation(intent);
TelecomAdapter.getInstance().setInCallService(this);
+ CallRecorder.getInstance().setUp(getApplicationContext());
return super.onBind(intent);
}
diff --git a/src/com/android/incallui/Presenter.java b/src/com/android/incallui/Presenter.java
index 4e1fa978..b0ad9fdb 100644
--- a/src/com/android/incallui/Presenter.java
+++ b/src/com/android/incallui/Presenter.java
@@ -17,6 +17,8 @@
package com.android.incallui;
import android.os.Bundle;
+import android.content.Context;
+import android.content.SharedPreferences;
/**
* Base class for Presenters.
@@ -56,4 +58,12 @@ public abstract class Presenter<U extends Ui> {
public U getUi() {
return mUi;
}
+
+ public static SharedPreferences getPrefs(Context context) {
+ // This replicates PreferenceManager.getDefaultSharedPreferences, except
+ // that we need multi process preferences, as the pref is written in a separate
+ // process (com.android.dialer vs. com.android.incallui)
+ final String prefName = context.getPackageName() + "_preferences";
+ return context.getSharedPreferences(prefName, Context.MODE_MULTI_PROCESS);
+ }
}