diff options
20 files changed, 729 insertions, 4 deletions
diff --git a/Android.mk b/Android.mk index 16f763761..5c93563df 100644 --- a/Android.mk +++ b/Android.mk @@ -9,7 +9,7 @@ incallui_dir := ../InCallUI src_dirs := src $(contacts_common_dir)/src $(incallui_dir)/src res_dirs := res $(contacts_common_dir)/res $(incallui_dir)/res -LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) +LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) $(call all-Iaidl-files-under, $(src_dirs)) LOCAL_SRC_FILES += ../../providers/ContactsProvider/src/com/android/providers/contacts/NameSplitter.java \ ../../providers/ContactsProvider/src/com/android/providers/contacts/HanziToPinyin.java \ ../../providers/ContactsProvider/src/com/android/providers/contacts/util/NeededForTesting.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0b4cf6432..e6dd30e95 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -61,6 +61,7 @@ <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="com.qti.permission.DIAG" /> <uses-permission android:name="com.qti.permission.IMS" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> <application android:name="DialerApplication" @@ -339,6 +340,10 @@ </intent-filter> </service> + <service android:name="com.android.services.callrecorder.CallRecorderService" + android:process="com.android.incallui"> + </service> + <!-- BroadcastReceiver for receiving Intents from Notification mechanism. --> <receiver android:name="com.android.incallui.InCallApp$NotificationBroadcastReceiver" android:exported="false" diff --git a/res/drawable-hdpi/ic_playback_dk.png b/res/drawable-hdpi/ic_playback_dk.png Binary files differnew file mode 100644 index 000000000..54b4e55fb --- /dev/null +++ b/res/drawable-hdpi/ic_playback_dk.png diff --git a/res/drawable-hdpi/ic_playback_stop_dk.png b/res/drawable-hdpi/ic_playback_stop_dk.png Binary files differnew file mode 100644 index 000000000..af58de533 --- /dev/null +++ b/res/drawable-hdpi/ic_playback_stop_dk.png diff --git a/res/drawable-mdpi/ic_playback_dk.png b/res/drawable-mdpi/ic_playback_dk.png Binary files differnew file mode 100644 index 000000000..4b277e341 --- /dev/null +++ b/res/drawable-mdpi/ic_playback_dk.png diff --git a/res/drawable-mdpi/ic_playback_stop_dk.png b/res/drawable-mdpi/ic_playback_stop_dk.png Binary files differnew file mode 100644 index 000000000..d9cb874d7 --- /dev/null +++ b/res/drawable-mdpi/ic_playback_stop_dk.png diff --git a/res/drawable-xhdpi/ic_playback_dk.png b/res/drawable-xhdpi/ic_playback_dk.png Binary files differnew file mode 100644 index 000000000..8a5c28df0 --- /dev/null +++ b/res/drawable-xhdpi/ic_playback_dk.png diff --git a/res/drawable-xhdpi/ic_playback_stop_dk.png b/res/drawable-xhdpi/ic_playback_stop_dk.png Binary files differnew file mode 100644 index 000000000..0c5ba2260 --- /dev/null +++ b/res/drawable-xhdpi/ic_playback_stop_dk.png diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml index 969169f25..5f319bcb8 100644 --- a/res/layout/call_detail.xml +++ b/res/layout/call_detail.xml @@ -29,7 +29,7 @@ <ListView android:id="@+id/history" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_alignParentTop="true" /> diff --git a/res/layout/call_detail_history_item.xml b/res/layout/call_detail_history_item.xml index 5b3fff712..a5acf5258 100644 --- a/res/layout/call_detail_history_item.xml +++ b/res/layout/call_detail_history_item.xml @@ -61,4 +61,11 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?attr/call_log_secondary_text_color" /> + + <LinearLayout + android:id="@+id/recording_playback_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + /> </LinearLayout> diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index c46c989c7..00bd29efc 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -56,4 +56,9 @@ <!-- Forward lookup --> <string name="nearby_places">Nearby places</string> <string name="people">People</string> + + <string name="start_call_playback">Play recording</string> + <string name="stop_call_playback">Stop</string> + <string name="call_playback_error_message">Failed to play recording</string> + </resources> diff --git a/res/values/config.xml b/res/values/config.xml new file mode 100644 index 000000000..e8b6eca2b --- /dev/null +++ b/res/values/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<resources> + <bool name="call_recording_enabled">false</bool> + <integer name="call_recording_audio_source">1</integer> +</resources> diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java index fe50c1b6f..516aac3de 100644 --- a/src/com/android/dialer/CallDetailActivity.java +++ b/src/com/android/dialer/CallDetailActivity.java @@ -62,10 +62,12 @@ import com.android.dialer.calllog.PhoneNumberDisplayHelper; import com.android.dialer.calllog.PhoneNumberUtilsWrapper; import com.android.dialer.util.AsyncTaskExecutor; import com.android.dialer.util.AsyncTaskExecutors; +import com.android.dialer.util.CallRecordingPlayer; import com.android.dialer.voicemail.VoicemailPlaybackFragment; import com.android.dialer.voicemail.VoicemailStatusHelper; import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage; import com.android.dialer.voicemail.VoicemailStatusHelperImpl; +import com.android.services.callrecorder.CallRecordingDataStore; import java.util.List; @@ -134,6 +136,9 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware private ProximitySensorManager mProximitySensorManager; private final ProximitySensorListener mProximitySensorListener = new ProximitySensorListener(); + private CallRecordingDataStore mCallRecordingDataStore = new CallRecordingDataStore(); + private CallRecordingPlayer mCallRecordingPlayer = new CallRecordingPlayer(); + /** Listener to changes in the proximity sensor state. */ private class ProximitySensorListener implements ProximitySensorManager.Listener { /** Used to show a blank view and hide the action bar. */ @@ -235,6 +240,13 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware } @Override + protected void onDestroy() { + super.onDestroy(); + mCallRecordingDataStore.close(); + mCallRecordingPlayer.stop(); + } + + @Override public void onResume() { super.onResume(); updateData(getCallLogEntryUris()); @@ -380,7 +392,8 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater, mCallTypeHelper, details, hasVoicemail(), mCallDetailHeader.canPlaceCallsTo(), - findViewById(R.id.controls))); + findViewById(R.id.controls), + mCallRecordingDataStore, mCallRecordingPlayer)); BackScrollManager.bind( new ScrollableHeader() { private View mControls = findViewById(R.id.controls); diff --git a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java index 8af3b82bb..4191f9ba9 100644 --- a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java +++ b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java @@ -23,10 +23,17 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.Button; import android.widget.TextView; import com.android.dialer.PhoneCallDetails; import com.android.dialer.R; +import com.android.dialer.util.CallRecordingPlayer; +import com.android.services.callrecorder.common.CallRecording; +import com.android.services.callrecorder.CallRecorderService; +import com.android.services.callrecorder.CallRecordingDataStore; + +import java.util.List; /** * Adapter for a ListView containing history items from the details of a call. @@ -59,9 +66,14 @@ public class CallDetailHistoryAdapter extends BaseAdapter { } }; + private CallRecordingDataStore mCallRecordingDataStore; + private CallRecordingPlayer mCallRecordingPlayer; + public CallDetailHistoryAdapter(Context context, LayoutInflater layoutInflater, CallTypeHelper callTypeHelper, PhoneCallDetails[] phoneCallDetails, - boolean showVoicemail, boolean showCallAndSms, View controls) { + boolean showVoicemail, boolean showCallAndSms, View controls, + CallRecordingDataStore callRecordingDataStore, + CallRecordingPlayer callRecordingPlayer) { mContext = context; mLayoutInflater = layoutInflater; mCallTypeHelper = callTypeHelper; @@ -69,6 +81,8 @@ public class CallDetailHistoryAdapter extends BaseAdapter { mShowVoicemail = showVoicemail; mShowCallAndSms = showCallAndSms; mControls = controls; + mCallRecordingDataStore = callRecordingDataStore; + mCallRecordingPlayer = callRecordingPlayer; } @Override @@ -157,6 +171,22 @@ public class CallDetailHistoryAdapter extends BaseAdapter { durationView.setText(formatDuration(details.duration)); } + // do this synchronously to prevent recordings from "popping in" + // after detail item is displayed + if (CallRecorderService.isEnabled(mContext)) { + mCallRecordingDataStore.open(mContext); // opens unless already open + List<CallRecording> recordings = + mCallRecordingDataStore.getRecordings(details.number.toString(), details.date); + + ViewGroup playbackView = + (ViewGroup) result.findViewById(R.id.recording_playback_layout); + playbackView.removeAllViews(); + for (CallRecording recording : recordings) { + Button button = mCallRecordingPlayer.createPlaybackButton(mContext, recording); + playbackView.addView(button); + } + } + return result; } diff --git a/src/com/android/dialer/util/CallRecordingPlayer.java b/src/com/android/dialer/util/CallRecordingPlayer.java new file mode 100644 index 000000000..9f7c143bd --- /dev/null +++ b/src/com/android/dialer/util/CallRecordingPlayer.java @@ -0,0 +1,154 @@ +/* + * 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.dialer.util; + +import android.content.Context; +import android.graphics.Color; +import android.media.MediaPlayer; +import android.os.Environment; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import com.android.dialer.R; +import com.android.services.callrecorder.CallRecorderService; +import com.android.services.callrecorder.common.CallRecording; + +import java.io.File; +import java.io.IOException; + +/** + * Simple playback for call recordings + */ +public class CallRecordingPlayer implements MediaPlayer.OnCompletionListener { + private static final String TAG = "CallRecordingPlayer"; + + private MediaPlayer mPlayer = null; + private boolean mPlaying = false; + private PlayButton mButton; + + public Button createPlaybackButton(Context context, CallRecording recording) { + return new PlayButton(context, recording, this); + } + + // button to toggle playback for a call recording + private static class PlayButton extends Button implements View.OnClickListener { + private boolean mPlaying = false; + private CallRecording mRecording; + private CallRecordingPlayer mPlayer; + + public PlayButton(Context context, CallRecording recording, CallRecordingPlayer player) { + super(context); + mRecording = recording; + mPlayer = player; + reset(); + setBackgroundColor(Color.TRANSPARENT); + setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (!mPlaying) { + mPlayer.play(mRecording, this); + if (!mPlayer.isPlaying()) { + Toast.makeText(mContext, R.string.call_playback_error_message, + Toast.LENGTH_SHORT).show(); + } + } else { + mPlayer.stop(); + } + + mPlaying = mPlayer.isPlaying(); + updateState(); + } + + private void updateState() { + setText(mPlaying ? R.string.stop_call_playback : R.string.start_call_playback); + setCompoundDrawablesRelativeWithIntrinsicBounds(mPlaying + ? R.drawable.ic_playback_stop_dk : R.drawable.ic_playback_dk, + 0, 0, 0); + } + + public void reset() { + mPlaying = false; + updateState(); + } + } + + private void play(CallRecording recording, PlayButton button) { + if (mPlayer != null) { + // stop and cleanup current session first + stop(); + } + + mButton = button; + + String filePath = recording.getFile().getAbsolutePath(); + + mPlayer = new MediaPlayer(); + mPlayer.setOnCompletionListener(this); + try { + mPlayer.setDataSource(filePath); + mPlayer.prepare(); + } catch (IOException e) { + Log.w(TAG, "Error opening " + filePath, e); + return; + } + + try { + mPlayer.start(); + mPlaying = true; + } catch (IllegalStateException e) { + Log.w(TAG, "Could not start player", e); + } + } + + public void stop() { + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + Log.w(TAG, "Exception stopping player", e); + } + mPlayer.release(); + mPlayer = null; + resetButton(); + } + mPlaying = false; + } + + private boolean isPlaying() { + return mPlaying; + } + + @Override + public void onCompletion(MediaPlayer mp) { + resetButton(); + + mPlayer.release(); + mPlayer = null; + mPlaying = false; + } + + private void resetButton() { + if (mButton != null) { + mButton.reset(); + mButton = null; + } + } +} diff --git a/src/com/android/services/callrecorder/CallRecorderService.java b/src/com/android/services/callrecorder/CallRecorderService.java new file mode 100644 index 000000000..25c5974df --- /dev/null +++ b/src/com/android/services/callrecorder/CallRecorderService.java @@ -0,0 +1,190 @@ +/* + * 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.services.callrecorder; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.MediaRecorder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.services.callrecorder.common.CallRecording; +import com.android.services.callrecorder.common.ICallRecorderService; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.android.dialer.R; + +public class CallRecorderService extends Service { + private static final String TAG = "CallRecorderService"; + private static final boolean DBG = false; + + private static enum RecorderState { + IDLE, + RECORDING + }; + + private MediaRecorder mMediaRecorder = null; + private RecorderState mState = RecorderState.IDLE; + private CallRecording mCurrentRecording = null; + + private static final String ENABLE_PROPERTY = "persist.call_recording.enabled"; + private static final String AUDIO_SOURCE_PROPERTY = "persist.call_recording.src"; + + private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + + private final ICallRecorderService.Stub mBinder = new ICallRecorderService.Stub() { + @Override + public CallRecording stopRecording() { + if (getState() == RecorderState.RECORDING) { + stopRecordingInternal(); + return mCurrentRecording; + } + return null; + } + + @Override + public boolean startRecording(String phoneNumber, long creationTime) + throws RemoteException { + String fileName = generateFilename(); + mCurrentRecording = new CallRecording(phoneNumber, creationTime, + fileName, System.currentTimeMillis()); + return startRecordingInternal(mCurrentRecording.getFile()); + + } + + @Override + public boolean isRecording() throws RemoteException { + return getState() == RecorderState.RECORDING; + } + + @Override + public CallRecording getActiveRecording() throws RemoteException { + return mCurrentRecording; + } + }; + + @Override + public void onCreate() { + if (DBG) Log.d(TAG, "Creating CallRecorderService"); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private int getAudioSource() { + int defaultValue = getResources().getInteger(R.integer.call_recording_audio_source); + return SystemProperties.getInt(AUDIO_SOURCE_PROPERTY, defaultValue); + } + + private synchronized boolean startRecordingInternal(File file) { + if (mMediaRecorder != null) { + if (DBG) { + Log.d(TAG, "Start called with recording in progress, stopping current recording"); + } + stopRecordingInternal(); + } + + if (DBG) Log.d(TAG, "Starting recording"); + + mMediaRecorder = new MediaRecorder(); + try { + int audioSource = getAudioSource(); + if (DBG) Log.d(TAG, "Creating media recorder with audio source " + audioSource); + mMediaRecorder.setAudioSource(audioSource); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); + } catch (IllegalStateException e) { + Log.w(TAG, "Error initializing media recorder", e); + return false; + } + + file.getParentFile().mkdirs(); + String outputPath = file.getAbsolutePath(); + if (DBG) Log.d(TAG, "Writing output to file " + outputPath); + + try { + mMediaRecorder.setOutputFile(outputPath); + mMediaRecorder.prepare(); + mMediaRecorder.start(); + mState = RecorderState.RECORDING; + return true; + } catch (IOException e) { + Log.w(TAG, "Could not start recording for file " + outputPath, e); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not start recording for file " + outputPath, e); + } catch (RuntimeException e) { + // only catch exceptions thrown by the MediaRecorder JNI code + if (e.getMessage().indexOf("start failed") >= 0) { + Log.w(TAG, "Could not start recording for file " + outputPath, e); + } else { + throw e; + } + } + + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + + return false; + } + + private synchronized void stopRecordingInternal() { + if (DBG) Log.d(TAG, "Stopping current recording"); + if (mMediaRecorder != null) { + try { + if (getState() == RecorderState.RECORDING) { + mMediaRecorder.stop(); + mMediaRecorder.reset(); + mMediaRecorder.release(); + } + } catch (IllegalStateException e) { + Log.e(TAG, "Exception closing media recorder", e); + } + mMediaRecorder = null; + mState = RecorderState.IDLE; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (DBG) Log.d(TAG, "Destroying CallRecorderService"); + } + + private synchronized RecorderState getState() { + return mState; + } + + private String generateFilename() { + String timestamp = DATE_FORMAT.format(new Date()); + return "callrecorder_" + timestamp + ".amr"; + } + + public static boolean isEnabled(Context context) { + boolean defaultValue = context.getResources().getBoolean(R.bool.call_recording_enabled); + return SystemProperties.getBoolean(ENABLE_PROPERTY, defaultValue); + } +} diff --git a/src/com/android/services/callrecorder/CallRecordingDataStore.java b/src/com/android/services/callrecorder/CallRecordingDataStore.java new file mode 100644 index 000000000..4310c1ad2 --- /dev/null +++ b/src/com/android/services/callrecorder/CallRecordingDataStore.java @@ -0,0 +1,177 @@ +/* + * 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.services.callrecorder; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; +import android.util.Log; + +import com.android.services.callrecorder.common.CallRecording; + +import java.util.ArrayList; +import java.util.List; + +/** + * Persistent data store for call recordings. Usage: + * open() + * read/write operations + * close() + */ +public class CallRecordingDataStore { + private static final String TAG = "CallRecordingStore"; + private SQLiteOpenHelper mOpenHelper = null; + private SQLiteDatabase mDatabase = null; + + /** + * Open before reading/writing. Will not open handle if one is already open. + */ + public void open(Context context) { + if (mDatabase == null) { + mOpenHelper = new CallRecordingSQLiteOpenHelper(context); + mDatabase = mOpenHelper.getWritableDatabase(); + } + } + + /** + * close when finished reading/writing + */ + public void close() { + if (mDatabase != null) { + mDatabase.close(); + } + if (mOpenHelper != null) { + mOpenHelper.close(); + } + mDatabase = null; + mOpenHelper = null; + } + + /** + * Save a recording in the data store + * + * @param recording the recording to store + */ + public void putRecording(CallRecording recording) { + final String insertSql = "INSERT INTO " + + CallRecordingsContract.CallRecording.TABLE_NAME + " (" + + CallRecordingsContract.CallRecording.COLUMN_NAME_PHONE_NUMBER + ", " + + CallRecordingsContract.CallRecording.COLUMN_NAME_CALL_DATE + ", " + + CallRecordingsContract.CallRecording.COLUMN_NAME_RECORDING_FILENAME + ", " + + CallRecordingsContract.CallRecording.COLUMN_NAME_CREATION_DATE + ") " + + " VALUES (?, ?, ?, ?)"; + + try { + SQLiteStatement stmt = mDatabase.compileStatement(insertSql); + int idx = 1; + stmt.bindString(idx++, recording.phoneNumber); + stmt.bindLong(idx++, recording.creationTime); + stmt.bindString(idx++, recording.fileName); + stmt.bindLong(idx++, System.currentTimeMillis()); + long id = stmt.executeInsert(); + Log.i(TAG, "Saved recording " + recording + " with id " + id); + } catch (SQLiteException e) { + Log.w(TAG, "Failed to save recording " + recording, e); + } + } + + /** + * Get all recordings associated with a phone call + * + * @param phoneNumber phone number no spaces + * @param callCreationDate time that the call was created + * @return list of recordings + */ + public List<CallRecording> getRecordings(String phoneNumber, long callCreationDate) { + List<CallRecording> resultList = new ArrayList<CallRecording>(); + + final String query = "SELECT " + + CallRecordingsContract.CallRecording.COLUMN_NAME_RECORDING_FILENAME + + " FROM " + CallRecordingsContract.CallRecording.TABLE_NAME + + " WHERE " + CallRecordingsContract.CallRecording.COLUMN_NAME_PHONE_NUMBER + " = ?" + + " AND " + CallRecordingsContract.CallRecording.COLUMN_NAME_CALL_DATE + " = ?" + + " ORDER BY " + CallRecordingsContract.CallRecording.COLUMN_NAME_CREATION_DATE; + + String args[] = { + phoneNumber, String.valueOf(callCreationDate) + }; + + try { + Cursor cursor = mDatabase.rawQuery(query, args); + while (cursor.moveToNext()) { + String fileName = cursor.getString(0); + CallRecording recording = + new CallRecording(phoneNumber, callCreationDate, fileName, 0); + if (recording.getFile().exists()) { + resultList.add(recording); + } + } + cursor.close(); + } catch (SQLiteException e) { + Log.w(TAG, "Failed to fetch recordings for number " + phoneNumber + + ", date " + callCreationDate, e); + } + + return resultList; + } + + static class CallRecordingsContract { + static interface CallRecording extends BaseColumns { + static final String TABLE_NAME = "call_recordings"; + static final String COLUMN_NAME_PHONE_NUMBER = "phone_number"; + static final String COLUMN_NAME_CALL_DATE = "call_date"; + static final String COLUMN_NAME_RECORDING_FILENAME = "recording_filename"; + static final String COLUMN_NAME_CREATION_DATE = "creation_date"; + } + } + + static class CallRecordingSQLiteOpenHelper extends SQLiteOpenHelper { + private static final int VERSION = 1; + private static final String DB_NAME = "callrecordings.db"; + + public CallRecordingSQLiteOpenHelper(Context context) { + super(context, DB_NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + CallRecordingsContract.CallRecording.TABLE_NAME + " (" + + CallRecordingsContract.CallRecording._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + CallRecordingsContract.CallRecording.COLUMN_NAME_PHONE_NUMBER + " TEXT," + + CallRecordingsContract.CallRecording.COLUMN_NAME_CALL_DATE + " LONG," + + CallRecordingsContract.CallRecording.COLUMN_NAME_RECORDING_FILENAME + " TEXT, " + + CallRecordingsContract.CallRecording.COLUMN_NAME_CREATION_DATE + " LONG" + + ");" + ); + + db.execSQL("CREATE INDEX IF NOT EXISTS phone_number_call_date_index ON " + + CallRecordingsContract.CallRecording.TABLE_NAME + " (" + + CallRecordingsContract.CallRecording.COLUMN_NAME_PHONE_NUMBER + ", " + + CallRecordingsContract.CallRecording.COLUMN_NAME_CALL_DATE + ");" + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // implement if we change the schema + } + } +} diff --git a/src/com/android/services/callrecorder/common/CallRecording.aidl b/src/com/android/services/callrecorder/common/CallRecording.aidl new file mode 100644 index 000000000..5ab84a09e --- /dev/null +++ b/src/com/android/services/callrecorder/common/CallRecording.aidl @@ -0,0 +1,3 @@ +package com.android.services.callrecorder.common; + +parcelable CallRecording; diff --git a/src/com/android/services/callrecorder/common/CallRecording.java b/src/com/android/services/callrecorder/common/CallRecording.java new file mode 100644 index 000000000..0bb192f8d --- /dev/null +++ b/src/com/android/services/callrecorder/common/CallRecording.java @@ -0,0 +1,82 @@ +/* + * 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.services.callrecorder.common; + +import android.os.Environment; +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.File; + +public final class CallRecording implements Parcelable { + public String phoneNumber; + public long creationTime; + public String fileName; + public long startRecordingTime; + + private static final String PUBLIC_DIRECTORY_NAME = "CallRecordings"; + + public static final Parcelable.Creator<CallRecording> CREATOR = new + Parcelable.Creator<CallRecording>() { + public CallRecording createFromParcel(Parcel in) { + return new CallRecording(in); + } + + public CallRecording[] newArray(int size) { + return new CallRecording[size]; + } + }; + + public CallRecording(String phoneNumber, long creationTime, + String fileName, long startRecordingTime) { + this.phoneNumber = phoneNumber; + this.creationTime = creationTime; + this.fileName = fileName; + this.startRecordingTime = startRecordingTime; + } + + public CallRecording(Parcel in) { + phoneNumber = in.readString(); + creationTime = in.readLong(); + fileName = in.readString(); + startRecordingTime = in.readLong(); + } + + public File getFile() { + File dir = Environment.getExternalStoragePublicDirectory(PUBLIC_DIRECTORY_NAME); + return new File(dir, fileName); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(phoneNumber); + out.writeLong(creationTime); + out.writeString(fileName); + out.writeLong(startRecordingTime); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "phoneNumber=" + phoneNumber + ", creationTime=" + creationTime + + ", fileName=" + fileName + ", startRecordingTime=" + startRecordingTime; + } +} diff --git a/src/com/android/services/callrecorder/common/ICallRecorderService.aidl b/src/com/android/services/callrecorder/common/ICallRecorderService.aidl new file mode 100644 index 000000000..d6d96d712 --- /dev/null +++ b/src/com/android/services/callrecorder/common/ICallRecorderService.aidl @@ -0,0 +1,39 @@ +package com.android.services.callrecorder.common; + +import com.android.services.callrecorder.common.CallRecording; + +/** + * Service for recording phone calls. Only one recording may be active at a time + * (i.e. every call to startRecording should be followed by a call to stopRecording). + */ +interface ICallRecorderService { + + /** + * Start a recording. + * + * @return true if recording started successfully + */ + boolean startRecording(String phoneNumber, long creationTime); + + /** + * stops the current recording + * + * @return call recording data including the output filename + */ + CallRecording stopRecording(); + + /** + * Recording status + * + * @return true if there is an active recording + */ + boolean isRecording(); + + /** + * Get recording currently in progress + * + * @return call recording object + */ + CallRecording getActiveRecording(); + +} |