Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.screenrecord; |
| 18 | |
| 19 | import android.app.Activity; |
| 20 | import android.app.Notification; |
| 21 | import android.app.NotificationChannel; |
| 22 | import android.app.NotificationManager; |
| 23 | import android.app.PendingIntent; |
| 24 | import android.app.Service; |
Beth Thibodeau | be08b6b | 2019-07-24 15:23:54 -0400 | [diff] [blame] | 25 | import android.content.ContentResolver; |
| 26 | import android.content.ContentValues; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 27 | import android.content.Context; |
| 28 | import android.content.Intent; |
| 29 | import android.graphics.Bitmap; |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 30 | import android.graphics.Point; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 31 | import android.graphics.drawable.Icon; |
| 32 | import android.hardware.display.DisplayManager; |
| 33 | import android.hardware.display.VirtualDisplay; |
| 34 | import android.media.MediaRecorder; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 35 | import android.media.projection.MediaProjection; |
| 36 | import android.media.projection.MediaProjectionManager; |
| 37 | import android.net.Uri; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 38 | import android.os.IBinder; |
| 39 | import android.provider.MediaStore; |
| 40 | import android.provider.Settings; |
| 41 | import android.util.DisplayMetrics; |
| 42 | import android.util.Log; |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 43 | import android.util.Size; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 44 | import android.view.Surface; |
| 45 | import android.widget.Toast; |
| 46 | |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 47 | import com.android.systemui.R; |
| 48 | |
| 49 | import java.io.File; |
| 50 | import java.io.IOException; |
Beth Thibodeau | be08b6b | 2019-07-24 15:23:54 -0400 | [diff] [blame] | 51 | import java.io.OutputStream; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 52 | import java.nio.file.Files; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 53 | import java.text.SimpleDateFormat; |
| 54 | import java.util.Date; |
| 55 | |
| 56 | /** |
| 57 | * A service which records the device screen and optionally microphone input. |
| 58 | */ |
| 59 | public class RecordingService extends Service { |
| 60 | private static final int NOTIFICATION_ID = 1; |
| 61 | private static final String TAG = "RecordingService"; |
| 62 | private static final String CHANNEL_ID = "screen_record"; |
| 63 | private static final String EXTRA_RESULT_CODE = "extra_resultCode"; |
| 64 | private static final String EXTRA_DATA = "extra_data"; |
| 65 | private static final String EXTRA_PATH = "extra_path"; |
| 66 | private static final String EXTRA_USE_AUDIO = "extra_useAudio"; |
| 67 | private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; |
| 68 | private static final int REQUEST_CODE = 2; |
| 69 | |
| 70 | private static final String ACTION_START = "com.android.systemui.screenrecord.START"; |
| 71 | private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; |
| 72 | private static final String ACTION_PAUSE = "com.android.systemui.screenrecord.PAUSE"; |
| 73 | private static final String ACTION_RESUME = "com.android.systemui.screenrecord.RESUME"; |
| 74 | private static final String ACTION_CANCEL = "com.android.systemui.screenrecord.CANCEL"; |
| 75 | private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; |
| 76 | private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE"; |
| 77 | |
| 78 | private static final int TOTAL_NUM_TRACKS = 1; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 79 | private static final int VIDEO_BIT_RATE = 6000000; |
| 80 | private static final int VIDEO_FRAME_RATE = 30; |
| 81 | private static final int AUDIO_BIT_RATE = 16; |
| 82 | private static final int AUDIO_SAMPLE_RATE = 44100; |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 83 | |
| 84 | private MediaProjectionManager mMediaProjectionManager; |
| 85 | private MediaProjection mMediaProjection; |
| 86 | private Surface mInputSurface; |
| 87 | private VirtualDisplay mVirtualDisplay; |
| 88 | private MediaRecorder mMediaRecorder; |
| 89 | private Notification.Builder mRecordingNotificationBuilder; |
| 90 | |
| 91 | private boolean mUseAudio; |
| 92 | private boolean mShowTaps; |
| 93 | private File mTempFile; |
| 94 | |
| 95 | /** |
| 96 | * Get an intent to start the recording service. |
| 97 | * |
| 98 | * @param context Context from the requesting activity |
| 99 | * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int, |
| 100 | * android.content.Intent)} |
| 101 | * @param data The data from {@link android.app.Activity#onActivityResult(int, int, |
| 102 | * android.content.Intent)} |
| 103 | * @param useAudio True to enable microphone input while recording |
| 104 | * @param showTaps True to make touches visible while recording |
| 105 | */ |
| 106 | public static Intent getStartIntent(Context context, int resultCode, Intent data, |
| 107 | boolean useAudio, boolean showTaps) { |
| 108 | return new Intent(context, RecordingService.class) |
| 109 | .setAction(ACTION_START) |
| 110 | .putExtra(EXTRA_RESULT_CODE, resultCode) |
| 111 | .putExtra(EXTRA_DATA, data) |
| 112 | .putExtra(EXTRA_USE_AUDIO, useAudio) |
| 113 | .putExtra(EXTRA_SHOW_TAPS, showTaps); |
| 114 | } |
| 115 | |
| 116 | @Override |
| 117 | public int onStartCommand(Intent intent, int flags, int startId) { |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 118 | if (intent == null) { |
| 119 | return Service.START_NOT_STICKY; |
| 120 | } |
| 121 | String action = intent.getAction(); |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 122 | Log.d(TAG, "onStartCommand " + action); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 123 | |
| 124 | NotificationManager notificationManager = |
| 125 | (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); |
| 126 | |
| 127 | switch (action) { |
| 128 | case ACTION_START: |
| 129 | int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED); |
| 130 | mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false); |
| 131 | mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); |
| 132 | Intent data = intent.getParcelableExtra(EXTRA_DATA); |
| 133 | if (data != null) { |
| 134 | mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data); |
| 135 | startRecording(); |
| 136 | } |
| 137 | break; |
| 138 | |
| 139 | case ACTION_CANCEL: |
| 140 | stopRecording(); |
| 141 | |
| 142 | // Delete temp file |
| 143 | if (!mTempFile.delete()) { |
| 144 | Log.e(TAG, "Error canceling screen recording!"); |
| 145 | Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) |
| 146 | .show(); |
| 147 | } else { |
| 148 | Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG) |
| 149 | .show(); |
| 150 | } |
| 151 | |
| 152 | // Close quick shade |
| 153 | sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); |
| 154 | break; |
| 155 | |
| 156 | case ACTION_STOP: |
| 157 | stopRecording(); |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 158 | saveRecording(notificationManager); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 159 | break; |
| 160 | |
| 161 | case ACTION_PAUSE: |
| 162 | mMediaRecorder.pause(); |
| 163 | setNotificationActions(true, notificationManager); |
| 164 | break; |
| 165 | |
| 166 | case ACTION_RESUME: |
| 167 | mMediaRecorder.resume(); |
| 168 | setNotificationActions(false, notificationManager); |
| 169 | break; |
| 170 | |
| 171 | case ACTION_SHARE: |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 172 | Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH)); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 173 | |
| 174 | Intent shareIntent = new Intent(Intent.ACTION_SEND) |
| 175 | .setType("video/mp4") |
| 176 | .putExtra(Intent.EXTRA_STREAM, shareUri); |
| 177 | String shareLabel = getResources().getString(R.string.screenrecord_share_label); |
| 178 | |
| 179 | // Close quick shade |
| 180 | sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); |
| 181 | |
| 182 | // Remove notification |
| 183 | notificationManager.cancel(NOTIFICATION_ID); |
| 184 | |
| 185 | startActivity(Intent.createChooser(shareIntent, shareLabel) |
| 186 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); |
| 187 | break; |
| 188 | case ACTION_DELETE: |
| 189 | // Close quick shade |
| 190 | sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); |
| 191 | |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 192 | ContentResolver resolver = getContentResolver(); |
| 193 | Uri uri = Uri.parse(intent.getStringExtra(EXTRA_PATH)); |
| 194 | resolver.delete(uri, null, null); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 195 | |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 196 | Toast.makeText( |
| 197 | this, |
| 198 | R.string.screenrecord_delete_description, |
| 199 | Toast.LENGTH_LONG).show(); |
| 200 | |
| 201 | // Remove notification |
| 202 | notificationManager.cancel(NOTIFICATION_ID); |
| 203 | Log.d(TAG, "Deleted recording " + uri); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 204 | break; |
| 205 | } |
| 206 | return Service.START_STICKY; |
| 207 | } |
| 208 | |
| 209 | @Override |
| 210 | public IBinder onBind(Intent intent) { |
| 211 | return null; |
| 212 | } |
| 213 | |
| 214 | @Override |
| 215 | public void onCreate() { |
| 216 | super.onCreate(); |
| 217 | |
| 218 | mMediaProjectionManager = |
| 219 | (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * Begin the recording session |
| 224 | */ |
| 225 | private void startRecording() { |
| 226 | try { |
| 227 | mTempFile = File.createTempFile("temp", ".mp4"); |
| 228 | Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath()); |
| 229 | |
| 230 | setTapsVisible(mShowTaps); |
| 231 | |
| 232 | // Set up media recorder |
| 233 | mMediaRecorder = new MediaRecorder(); |
| 234 | if (mUseAudio) { |
| 235 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); |
| 236 | } |
| 237 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); |
| 238 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); |
| 239 | |
| 240 | // Set up video |
| 241 | DisplayMetrics metrics = getResources().getDisplayMetrics(); |
| 242 | int screenWidth = metrics.widthPixels; |
| 243 | int screenHeight = metrics.heightPixels; |
| 244 | mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); |
| 245 | mMediaRecorder.setVideoSize(screenWidth, screenHeight); |
| 246 | mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE); |
| 247 | mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE); |
| 248 | |
| 249 | // Set up audio |
| 250 | if (mUseAudio) { |
| 251 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); |
| 252 | mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS); |
| 253 | mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE); |
| 254 | mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE); |
| 255 | } |
| 256 | |
| 257 | mMediaRecorder.setOutputFile(mTempFile); |
| 258 | mMediaRecorder.prepare(); |
| 259 | |
| 260 | // Create surface |
| 261 | mInputSurface = mMediaRecorder.getSurface(); |
| 262 | mVirtualDisplay = mMediaProjection.createVirtualDisplay( |
| 263 | "Recording Display", |
| 264 | screenWidth, |
| 265 | screenHeight, |
| 266 | metrics.densityDpi, |
| 267 | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, |
| 268 | mInputSurface, |
| 269 | null, |
| 270 | null); |
| 271 | |
| 272 | mMediaRecorder.start(); |
| 273 | } catch (IOException e) { |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 274 | Log.e(TAG, "Error starting screen recording: " + e.getMessage()); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 275 | e.printStackTrace(); |
| 276 | throw new RuntimeException(e); |
| 277 | } |
| 278 | |
| 279 | createRecordingNotification(); |
| 280 | } |
| 281 | |
| 282 | private void createRecordingNotification() { |
| 283 | NotificationChannel channel = new NotificationChannel( |
| 284 | CHANNEL_ID, |
| 285 | getString(R.string.screenrecord_name), |
| 286 | NotificationManager.IMPORTANCE_HIGH); |
| 287 | channel.setDescription(getString(R.string.screenrecord_channel_description)); |
| 288 | channel.enableVibration(true); |
| 289 | NotificationManager notificationManager = |
| 290 | (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); |
| 291 | notificationManager.createNotificationChannel(channel); |
| 292 | |
| 293 | mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) |
| 294 | .setSmallIcon(R.drawable.ic_android) |
| 295 | .setContentTitle(getResources().getString(R.string.screenrecord_name)) |
| 296 | .setUsesChronometer(true) |
| 297 | .setOngoing(true); |
| 298 | setNotificationActions(false, notificationManager); |
| 299 | Notification notification = mRecordingNotificationBuilder.build(); |
| 300 | startForeground(NOTIFICATION_ID, notification); |
| 301 | } |
| 302 | |
| 303 | private void setNotificationActions(boolean isPaused, NotificationManager notificationManager) { |
| 304 | String pauseString = getResources() |
| 305 | .getString(isPaused ? R.string.screenrecord_resume_label |
| 306 | : R.string.screenrecord_pause_label); |
| 307 | Intent pauseIntent = isPaused ? getResumeIntent(this) : getPauseIntent(this); |
| 308 | |
| 309 | mRecordingNotificationBuilder.setActions( |
| 310 | new Notification.Action.Builder( |
| 311 | Icon.createWithResource(this, R.drawable.ic_android), |
| 312 | getResources().getString(R.string.screenrecord_stop_label), |
| 313 | PendingIntent |
| 314 | .getService(this, REQUEST_CODE, getStopIntent(this), |
| 315 | PendingIntent.FLAG_UPDATE_CURRENT)) |
| 316 | .build(), |
| 317 | new Notification.Action.Builder( |
| 318 | Icon.createWithResource(this, R.drawable.ic_android), pauseString, |
| 319 | PendingIntent.getService(this, REQUEST_CODE, pauseIntent, |
| 320 | PendingIntent.FLAG_UPDATE_CURRENT)) |
| 321 | .build(), |
| 322 | new Notification.Action.Builder( |
| 323 | Icon.createWithResource(this, R.drawable.ic_android), |
| 324 | getResources().getString(R.string.screenrecord_cancel_label), |
| 325 | PendingIntent |
| 326 | .getService(this, REQUEST_CODE, getCancelIntent(this), |
| 327 | PendingIntent.FLAG_UPDATE_CURRENT)) |
| 328 | .build()); |
| 329 | notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build()); |
| 330 | } |
| 331 | |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 332 | private Notification createSaveNotification(Uri uri) { |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 333 | Intent viewIntent = new Intent(Intent.ACTION_VIEW) |
| 334 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION) |
Beth Thibodeau | be08b6b | 2019-07-24 15:23:54 -0400 | [diff] [blame] | 335 | .setDataAndType(uri, "video/mp4"); |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 336 | |
| 337 | Notification.Action shareAction = new Notification.Action.Builder( |
| 338 | Icon.createWithResource(this, R.drawable.ic_android), |
| 339 | getResources().getString(R.string.screenrecord_share_label), |
| 340 | PendingIntent.getService( |
| 341 | this, |
| 342 | REQUEST_CODE, |
Beth Thibodeau | be08b6b | 2019-07-24 15:23:54 -0400 | [diff] [blame] | 343 | getShareIntent(this, uri.toString()), |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 344 | PendingIntent.FLAG_UPDATE_CURRENT)) |
| 345 | .build(); |
| 346 | |
| 347 | Notification.Action deleteAction = new Notification.Action.Builder( |
| 348 | Icon.createWithResource(this, R.drawable.ic_android), |
| 349 | getResources().getString(R.string.screenrecord_delete_label), |
| 350 | PendingIntent.getService( |
| 351 | this, |
| 352 | REQUEST_CODE, |
Beth Thibodeau | be08b6b | 2019-07-24 15:23:54 -0400 | [diff] [blame] | 353 | getDeleteIntent(this, uri.toString()), |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 354 | PendingIntent.FLAG_UPDATE_CURRENT)) |
| 355 | .build(); |
| 356 | |
| 357 | Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) |
| 358 | .setSmallIcon(R.drawable.ic_android) |
| 359 | .setContentTitle(getResources().getString(R.string.screenrecord_name)) |
| 360 | .setContentText(getResources().getString(R.string.screenrecord_save_message)) |
| 361 | .setContentIntent(PendingIntent.getActivity( |
| 362 | this, |
| 363 | REQUEST_CODE, |
| 364 | viewIntent, |
| 365 | Intent.FLAG_GRANT_READ_URI_PERMISSION)) |
| 366 | .addAction(shareAction) |
| 367 | .addAction(deleteAction) |
| 368 | .setAutoCancel(true); |
| 369 | |
| 370 | // Add thumbnail if available |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 371 | Bitmap thumbnailBitmap = null; |
| 372 | try { |
| 373 | ContentResolver resolver = getContentResolver(); |
| 374 | Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE); |
| 375 | thumbnailBitmap = resolver.loadThumbnail(uri, size, null); |
| 376 | } catch (IOException e) { |
| 377 | Log.e(TAG, "Error creating thumbnail: " + e.getMessage()); |
| 378 | e.printStackTrace(); |
| 379 | } |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 380 | if (thumbnailBitmap != null) { |
| 381 | Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle() |
| 382 | .bigPicture(thumbnailBitmap) |
| 383 | .bigLargeIcon((Bitmap) null); |
| 384 | builder.setLargeIcon(thumbnailBitmap).setStyle(pictureStyle); |
| 385 | } |
| 386 | return builder.build(); |
| 387 | } |
| 388 | |
| 389 | private void stopRecording() { |
| 390 | setTapsVisible(false); |
| 391 | mMediaRecorder.stop(); |
| 392 | mMediaRecorder.release(); |
| 393 | mMediaRecorder = null; |
| 394 | mMediaProjection.stop(); |
| 395 | mMediaProjection = null; |
| 396 | mInputSurface.release(); |
| 397 | mVirtualDisplay.release(); |
| 398 | stopSelf(); |
| 399 | } |
| 400 | |
Beth Thibodeau | cf0ff86 | 2019-08-08 14:52:24 -0400 | [diff] [blame] | 401 | private void saveRecording(NotificationManager notificationManager) { |
| 402 | String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'") |
| 403 | .format(new Date()); |
| 404 | |
| 405 | ContentValues values = new ContentValues(); |
| 406 | values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName); |
| 407 | values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); |
| 408 | values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis()); |
| 409 | values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis()); |
| 410 | |
| 411 | ContentResolver resolver = getContentResolver(); |
| 412 | Uri collectionUri = MediaStore.Video.Media.getContentUri( |
| 413 | MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| 414 | Uri itemUri = resolver.insert(collectionUri, values); |
| 415 | |
| 416 | try { |
| 417 | // Add to the mediastore |
| 418 | OutputStream os = resolver.openOutputStream(itemUri, "w"); |
| 419 | Files.copy(mTempFile.toPath(), os); |
| 420 | os.close(); |
| 421 | |
| 422 | Notification notification = createSaveNotification(itemUri); |
| 423 | notificationManager.notify(NOTIFICATION_ID, notification); |
| 424 | |
| 425 | mTempFile.delete(); |
| 426 | } catch (IOException e) { |
| 427 | Log.e(TAG, "Error saving screen recording: " + e.getMessage()); |
| 428 | Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) |
| 429 | .show(); |
| 430 | } |
| 431 | } |
| 432 | |
Beth Thibodeau | 5898ac4 | 2018-10-26 13:00:09 -0400 | [diff] [blame] | 433 | private void setTapsVisible(boolean turnOn) { |
| 434 | int value = turnOn ? 1 : 0; |
| 435 | Settings.System.putInt(getApplicationContext().getContentResolver(), |
| 436 | Settings.System.SHOW_TOUCHES, value); |
| 437 | } |
| 438 | |
| 439 | private static Intent getStopIntent(Context context) { |
| 440 | return new Intent(context, RecordingService.class).setAction(ACTION_STOP); |
| 441 | } |
| 442 | |
| 443 | private static Intent getPauseIntent(Context context) { |
| 444 | return new Intent(context, RecordingService.class).setAction(ACTION_PAUSE); |
| 445 | } |
| 446 | |
| 447 | private static Intent getResumeIntent(Context context) { |
| 448 | return new Intent(context, RecordingService.class).setAction(ACTION_RESUME); |
| 449 | } |
| 450 | |
| 451 | private static Intent getCancelIntent(Context context) { |
| 452 | return new Intent(context, RecordingService.class).setAction(ACTION_CANCEL); |
| 453 | } |
| 454 | |
| 455 | private static Intent getShareIntent(Context context, String path) { |
| 456 | return new Intent(context, RecordingService.class).setAction(ACTION_SHARE) |
| 457 | .putExtra(EXTRA_PATH, path); |
| 458 | } |
| 459 | |
| 460 | private static Intent getDeleteIntent(Context context, String path) { |
| 461 | return new Intent(context, RecordingService.class).setAction(ACTION_DELETE) |
| 462 | .putExtra(EXTRA_PATH, path); |
| 463 | } |
| 464 | } |