Loading AndroidManifest.xml +2 −1 Original line number Diff line number Diff line Loading @@ -114,7 +114,8 @@ android:appCategory="social" android:supportsRtl="true" android:usesCleartextTraffic="false" android:extractNativeLibs="false"> android:extractNativeLibs="false" android:requestLegacyExternalStorage="true"> </application> </manifest> java/com/android/dialer/binary/common/DialerApplication.java +5 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.CallLogFramework; import com.android.dialer.calllog.config.CallLogConfig; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.callrecord.CallRecordingAutoMigrator; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.inject.HasRootComponent; Loading @@ -48,6 +49,10 @@ public abstract class DialerApplication extends Application implements HasRootCo new FilteredNumberAsyncQueryHandler(this), DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); new CallRecordingAutoMigrator( this.getApplicationContext(), DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); initializeAnnotatedCallLog(); PersistentLogger.initialize(this); Loading java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java +6 −5 Original line number Diff line number Diff line Loading @@ -17,10 +17,12 @@ package com.android.dialer.calldetails; import android.content.ActivityNotFoundException; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.CallLog.Calls; import android.provider.MediaStore; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; Loading @@ -45,14 +47,13 @@ import com.android.dialer.callrecord.CallRecording; import com.android.dialer.callrecord.CallRecordingDataStore; import com.android.dialer.callrecord.impl.CallRecorderService; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.Constants; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult.Type; import com.android.dialer.glidephotomanager.PhotoInfo; import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; Loading Loading @@ -269,9 +270,9 @@ public class CallDetailsEntryViewHolder extends ViewHolder { } private void playRecording(Context context, CallRecording recording) { Uri uri = FileProvider.getUriForFile(context, Constants.get().getFileProviderAuthority(), recording.getFile()); String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); Uri uri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, recording.mediaId); String extension = MimeTypeMap.getFileExtensionFromUrl(recording.fileName); String mime = !TextUtils.isEmpty(extension) ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : "audio/*"; try { Loading java/com/android/dialer/callrecord/CallRecording.java +29 −6 Original line number Diff line number Diff line Loading @@ -16,9 +16,13 @@ package com.android.dialer.callrecord; import android.content.ContentValues; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.provider.MediaStore; import android.text.TextUtils; import android.webkit.MimeTypeMap; import java.io.File; Loading @@ -27,8 +31,7 @@ public final class CallRecording implements Parcelable { public long creationTime; public String fileName; public long startRecordingTime; private static final String PUBLIC_DIRECTORY_NAME = "CallRecordings"; public long mediaId; public static final Parcelable.Creator<CallRecording> CREATOR = new Parcelable.Creator<CallRecording>() { Loading @@ -44,11 +47,12 @@ public final class CallRecording implements Parcelable { }; public CallRecording(String phoneNumber, long creationTime, String fileName, long startRecordingTime) { String fileName, long startRecordingTime, long mediaId) { this.phoneNumber = phoneNumber; this.creationTime = creationTime; this.fileName = fileName; this.startRecordingTime = startRecordingTime; this.mediaId = mediaId; } public CallRecording(Parcel in) { Loading @@ -56,11 +60,29 @@ public final class CallRecording implements Parcelable { creationTime = in.readLong(); fileName = in.readString(); startRecordingTime = in.readLong(); mediaId = in.readLong(); } public static ContentValues generateMediaInsertValues(String fileName, long creationTime) { final ContentValues cv = new ContentValues(5); cv.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Call Recordings"); cv.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName); cv.put(MediaStore.Audio.Media.DATE_TAKEN, creationTime); cv.put(MediaStore.Audio.Media.IS_PENDING, 1); final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName); final String mime = !TextUtils.isEmpty(extension) ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : "audio/*"; cv.put(MediaStore.Audio.Media.MIME_TYPE, mime); return cv; } public File getFile() { File dir = Environment.getExternalStoragePublicDirectory(PUBLIC_DIRECTORY_NAME); return new File(dir, fileName); public static ContentValues generateCompletedValues() { final ContentValues cv = new ContentValues(1); cv.put(MediaStore.Audio.Media.IS_PENDING, 0); return cv; } @Override Loading @@ -69,6 +91,7 @@ public final class CallRecording implements Parcelable { out.writeLong(creationTime); out.writeString(fileName); out.writeLong(startRecordingTime); out.writeLong(mediaId); } @Override Loading java/com/android/dialer/callrecord/CallRecordingAutoMigrator.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * Copyright (C) 2020 The LineageOS 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.callrecord; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.text.TextUtils; import android.util.SparseArray; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorFactory; import com.android.voicemail.impl.mail.utils.LogUtils; import libcore.io.IoUtils; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; public class CallRecordingAutoMigrator { private static final String TAG = "CallRecordingAutoMigrator"; @NonNull private final Context appContext; @NonNull private final DialerExecutorFactory dialerExecutorFactory; public CallRecordingAutoMigrator( @NonNull Context appContext, @NonNull DialerExecutorFactory dialerExecutorFactory) { this.appContext = Assert.isNotNull(appContext); this.dialerExecutorFactory = Assert.isNotNull(dialerExecutorFactory); } public void asyncAutoMigrate() { dialerExecutorFactory .createNonUiTaskBuilder(new ShouldAttemptAutoMigrate(appContext)) .onSuccess(this::autoMigrate) .build() .executeParallel(null); } @TargetApi(26) private void autoMigrate(boolean shouldAttemptAutoMigrate) { if (!shouldAttemptAutoMigrate) { return; } final CallRecordingDataStore store = new CallRecordingDataStore(); store.open(appContext); final ContentResolver cr = appContext.getContentResolver(); final SparseArray<CallRecording> oldRecordingData = store.getUnmigratedRecordingData(); final File dir = Environment.getExternalStoragePublicDirectory("CallRecordings"); for (File recording : dir.listFiles()) { OutputStream os = null; try { // determine data store ID and call creation time of recording int id = -1; long creationTime = System.currentTimeMillis(); for (int i = 0; i < oldRecordingData.size(); i++) { if (TextUtils.equals(recording.getName(), oldRecordingData.get(i).fileName)) { creationTime = oldRecordingData.get(i).creationTime; id = oldRecordingData.keyAt(i); break; } } // create media store entry for recording Uri uri = cr.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, CallRecording.generateMediaInsertValues(recording.getName(), creationTime)); os = cr.openOutputStream(uri); // copy file contents to media store stream Files.copy(recording.toPath(), os); // insert media store id to store if (id >= 0) { store.updateMigratedRecording(id, Integer.parseInt(uri.getLastPathSegment())); } // mark recording as complete cr.update(uri, CallRecording.generateCompletedValues(), null, null); // delete file LogUtils.i(TAG, "Successfully migrated recording " + recording + " (ID " + id + ")"); recording.delete(); } catch (IOException e) { LogUtils.w(TAG, "Failed migrating call recording " + recording, e); } finally { if (os != null) { IoUtils.closeQuietly(os); } } } if (dir.listFiles().length == 0) { dir.delete(); } store.close(); } private static class ShouldAttemptAutoMigrate implements Worker<Void, Boolean> { private final Context appContext; ShouldAttemptAutoMigrate(Context appContext) { this.appContext = appContext; } @Nullable @Override public Boolean doInBackground(@Nullable Void input) { if (Build.VERSION.SDK_INT < 26) { return false; } if (appContext.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { LogUtil.i(TAG, "not attempting auto-migrate: no storage permission"); return false; } final File dir = Environment.getExternalStoragePublicDirectory("CallRecordings"); if (!dir.exists()) { LogUtil.i(TAG, "not attempting auto-migrate: no recordings present"); return false; } return true; } } } Loading
AndroidManifest.xml +2 −1 Original line number Diff line number Diff line Loading @@ -114,7 +114,8 @@ android:appCategory="social" android:supportsRtl="true" android:usesCleartextTraffic="false" android:extractNativeLibs="false"> android:extractNativeLibs="false" android:requestLegacyExternalStorage="true"> </application> </manifest>
java/com/android/dialer/binary/common/DialerApplication.java +5 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.CallLogFramework; import com.android.dialer.calllog.config.CallLogConfig; import com.android.dialer.calllog.config.CallLogConfigComponent; import com.android.dialer.callrecord.CallRecordingAutoMigrator; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.inject.HasRootComponent; Loading @@ -48,6 +49,10 @@ public abstract class DialerApplication extends Application implements HasRootCo new FilteredNumberAsyncQueryHandler(this), DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); new CallRecordingAutoMigrator( this.getApplicationContext(), DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); initializeAnnotatedCallLog(); PersistentLogger.initialize(this); Loading
java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java +6 −5 Original line number Diff line number Diff line Loading @@ -17,10 +17,12 @@ package com.android.dialer.calldetails; import android.content.ActivityNotFoundException; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.CallLog.Calls; import android.provider.MediaStore; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; Loading @@ -45,14 +47,13 @@ import com.android.dialer.callrecord.CallRecording; import com.android.dialer.callrecord.CallRecordingDataStore; import com.android.dialer.callrecord.impl.CallRecorderService; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.Constants; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult.Type; import com.android.dialer.glidephotomanager.PhotoInfo; import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; Loading Loading @@ -269,9 +270,9 @@ public class CallDetailsEntryViewHolder extends ViewHolder { } private void playRecording(Context context, CallRecording recording) { Uri uri = FileProvider.getUriForFile(context, Constants.get().getFileProviderAuthority(), recording.getFile()); String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); Uri uri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, recording.mediaId); String extension = MimeTypeMap.getFileExtensionFromUrl(recording.fileName); String mime = !TextUtils.isEmpty(extension) ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : "audio/*"; try { Loading
java/com/android/dialer/callrecord/CallRecording.java +29 −6 Original line number Diff line number Diff line Loading @@ -16,9 +16,13 @@ package com.android.dialer.callrecord; import android.content.ContentValues; import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.provider.MediaStore; import android.text.TextUtils; import android.webkit.MimeTypeMap; import java.io.File; Loading @@ -27,8 +31,7 @@ public final class CallRecording implements Parcelable { public long creationTime; public String fileName; public long startRecordingTime; private static final String PUBLIC_DIRECTORY_NAME = "CallRecordings"; public long mediaId; public static final Parcelable.Creator<CallRecording> CREATOR = new Parcelable.Creator<CallRecording>() { Loading @@ -44,11 +47,12 @@ public final class CallRecording implements Parcelable { }; public CallRecording(String phoneNumber, long creationTime, String fileName, long startRecordingTime) { String fileName, long startRecordingTime, long mediaId) { this.phoneNumber = phoneNumber; this.creationTime = creationTime; this.fileName = fileName; this.startRecordingTime = startRecordingTime; this.mediaId = mediaId; } public CallRecording(Parcel in) { Loading @@ -56,11 +60,29 @@ public final class CallRecording implements Parcelable { creationTime = in.readLong(); fileName = in.readString(); startRecordingTime = in.readLong(); mediaId = in.readLong(); } public static ContentValues generateMediaInsertValues(String fileName, long creationTime) { final ContentValues cv = new ContentValues(5); cv.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Call Recordings"); cv.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName); cv.put(MediaStore.Audio.Media.DATE_TAKEN, creationTime); cv.put(MediaStore.Audio.Media.IS_PENDING, 1); final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName); final String mime = !TextUtils.isEmpty(extension) ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : "audio/*"; cv.put(MediaStore.Audio.Media.MIME_TYPE, mime); return cv; } public File getFile() { File dir = Environment.getExternalStoragePublicDirectory(PUBLIC_DIRECTORY_NAME); return new File(dir, fileName); public static ContentValues generateCompletedValues() { final ContentValues cv = new ContentValues(1); cv.put(MediaStore.Audio.Media.IS_PENDING, 0); return cv; } @Override Loading @@ -69,6 +91,7 @@ public final class CallRecording implements Parcelable { out.writeLong(creationTime); out.writeString(fileName); out.writeLong(startRecordingTime); out.writeLong(mediaId); } @Override Loading
java/com/android/dialer/callrecord/CallRecordingAutoMigrator.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * Copyright (C) 2020 The LineageOS 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.callrecord; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.text.TextUtils; import android.util.SparseArray; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorFactory; import com.android.voicemail.impl.mail.utils.LogUtils; import libcore.io.IoUtils; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; public class CallRecordingAutoMigrator { private static final String TAG = "CallRecordingAutoMigrator"; @NonNull private final Context appContext; @NonNull private final DialerExecutorFactory dialerExecutorFactory; public CallRecordingAutoMigrator( @NonNull Context appContext, @NonNull DialerExecutorFactory dialerExecutorFactory) { this.appContext = Assert.isNotNull(appContext); this.dialerExecutorFactory = Assert.isNotNull(dialerExecutorFactory); } public void asyncAutoMigrate() { dialerExecutorFactory .createNonUiTaskBuilder(new ShouldAttemptAutoMigrate(appContext)) .onSuccess(this::autoMigrate) .build() .executeParallel(null); } @TargetApi(26) private void autoMigrate(boolean shouldAttemptAutoMigrate) { if (!shouldAttemptAutoMigrate) { return; } final CallRecordingDataStore store = new CallRecordingDataStore(); store.open(appContext); final ContentResolver cr = appContext.getContentResolver(); final SparseArray<CallRecording> oldRecordingData = store.getUnmigratedRecordingData(); final File dir = Environment.getExternalStoragePublicDirectory("CallRecordings"); for (File recording : dir.listFiles()) { OutputStream os = null; try { // determine data store ID and call creation time of recording int id = -1; long creationTime = System.currentTimeMillis(); for (int i = 0; i < oldRecordingData.size(); i++) { if (TextUtils.equals(recording.getName(), oldRecordingData.get(i).fileName)) { creationTime = oldRecordingData.get(i).creationTime; id = oldRecordingData.keyAt(i); break; } } // create media store entry for recording Uri uri = cr.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, CallRecording.generateMediaInsertValues(recording.getName(), creationTime)); os = cr.openOutputStream(uri); // copy file contents to media store stream Files.copy(recording.toPath(), os); // insert media store id to store if (id >= 0) { store.updateMigratedRecording(id, Integer.parseInt(uri.getLastPathSegment())); } // mark recording as complete cr.update(uri, CallRecording.generateCompletedValues(), null, null); // delete file LogUtils.i(TAG, "Successfully migrated recording " + recording + " (ID " + id + ")"); recording.delete(); } catch (IOException e) { LogUtils.w(TAG, "Failed migrating call recording " + recording, e); } finally { if (os != null) { IoUtils.closeQuietly(os); } } } if (dir.listFiles().length == 0) { dir.delete(); } store.close(); } private static class ShouldAttemptAutoMigrate implements Worker<Void, Boolean> { private final Context appContext; ShouldAttemptAutoMigrate(Context appContext) { this.appContext = appContext; } @Nullable @Override public Boolean doInBackground(@Nullable Void input) { if (Build.VERSION.SDK_INT < 26) { return false; } if (appContext.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { LogUtil.i(TAG, "not attempting auto-migrate: no storage permission"); return false; } final File dir = Environment.getExternalStoragePublicDirectory("CallRecordings"); if (!dir.exists()) { LogUtil.i(TAG, "not attempting auto-migrate: no recordings present"); return false; } return true; } } }