diff options
author | Danny Baumann <dannybaumann@web.de> | 2016-02-01 14:07:17 +0100 |
---|---|---|
committer | Gerrit Code Review <gerrit@cyanogenmod.org> | 2016-02-10 04:19:16 -0800 |
commit | def25800f9a3acd5d4e8e063419ec95a139e0795 (patch) | |
tree | 61093495293019b93e12eced8f303f24fe66705f | |
parent | 0b8ca16a21f0656d9a10a9e413b601780f439469 (diff) | |
download | packages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.tar.gz packages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.tar.bz2 packages_apps_InCallUI-def25800f9a3acd5d4e8e063419ec95a139e0795.zip |
Re-add call recording feature.
Change-Id: I44f766b2ef52d76ace65a9603401ba3368f674b1
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 Binary files differnew file mode 100644 index 00000000..a98b837d --- /dev/null +++ b/res/drawable-hdpi/ic_recording_indicator.png diff --git a/res/drawable-hdpi/ic_toolbar_record.png b/res/drawable-hdpi/ic_toolbar_record.png Binary files differnew file mode 100644 index 00000000..b395cdbd --- /dev/null +++ b/res/drawable-hdpi/ic_toolbar_record.png diff --git a/res/drawable-mdpi/ic_recording_indicator.png b/res/drawable-mdpi/ic_recording_indicator.png Binary files differnew file mode 100644 index 00000000..2a4c19ee --- /dev/null +++ b/res/drawable-mdpi/ic_recording_indicator.png diff --git a/res/drawable-mdpi/ic_toolbar_record.png b/res/drawable-mdpi/ic_toolbar_record.png Binary files differnew file mode 100644 index 00000000..4a99405d --- /dev/null +++ b/res/drawable-mdpi/ic_toolbar_record.png diff --git a/res/drawable-xhdpi/ic_recording_indicator.png b/res/drawable-xhdpi/ic_recording_indicator.png Binary files differnew file mode 100644 index 00000000..33e68751 --- /dev/null +++ b/res/drawable-xhdpi/ic_recording_indicator.png diff --git a/res/drawable-xhdpi/ic_toolbar_record.png b/res/drawable-xhdpi/ic_toolbar_record.png Binary files differnew file mode 100644 index 00000000..b2fc680c --- /dev/null +++ b/res/drawable-xhdpi/ic_toolbar_record.png diff --git a/res/drawable-xxhdpi/ic_toolbar_record.png b/res/drawable-xxhdpi/ic_toolbar_record.png Binary files differnew file mode 100644 index 00000000..b1fd09d1 --- /dev/null +++ b/res/drawable-xxhdpi/ic_toolbar_record.png 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); + } } |