summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2018-11-01 23:14:14 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-11-01 23:14:14 +0000
commit005489c07e92951a7b92df88c94de906a4699620 (patch)
tree7c5a3621c26b69530742a654eb9747730abd53be
parent4c2ca62956e59f36d95b0243f7ff7e50d9c80588 (diff)
parent5898ac47b2b61c64416bfedaa47afecb5da2a33d (diff)
Merge "Adding screen recording function."
-rw-r--r--core/java/android/util/FeatureFlagUtils.java2
-rw-r--r--core/java/com/android/internal/util/ScreenRecordHelper.java52
-rw-r--r--packages/SystemUI/AndroidManifest.xml8
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog.xml41
-rw-r--r--packages/SystemUI/res/values/strings.xml33
-rw-r--r--packages/SystemUI/res/values/styles.xml9
-rw-r--r--packages/SystemUI/res/xml/fileprovider.xml1
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java451
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java135
10 files changed, 746 insertions, 1 deletions
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index fb44eda5bc73..2d67d79062b9 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -36,6 +36,7 @@ public class FeatureFlagUtils {
public static final String PERSIST_PREFIX = "persist." + FFLAG_OVERRIDE_PREFIX;
public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
public static final String EMERGENCY_DIAL_SHORTCUTS = "settings_emergency_dial_shortcuts";
+ public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -50,6 +51,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "true");
DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false");
+ DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
}
/**
diff --git a/core/java/com/android/internal/util/ScreenRecordHelper.java b/core/java/com/android/internal/util/ScreenRecordHelper.java
new file mode 100644
index 000000000000..64d089848b7d
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenRecordHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.internal.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+public class ScreenRecordHelper {
+ private static final String SYSUI_PACKAGE = "com.android.systemui";
+ private static final String SYSUI_SCREENRECORD_LAUNCHER =
+ "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+ private final Context mContext;
+
+ /**
+ * Create a new ScreenRecordHelper for the given context
+ * @param context
+ */
+ public ScreenRecordHelper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Show dialog of screen recording options to user.
+ */
+ public void launchRecordPrompt() {
+ final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENRECORD_LAUNCHER);
+ final Intent intent = new Intent();
+ intent.setComponent(launcherComponent);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 44bc3f212654..d9b20637e6b4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -136,6 +136,10 @@
<!-- Screen Capturing -->
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <!-- Screen Recording -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -267,6 +271,10 @@
</intent-filter>
</receiver>
+ <activity android:name=".screenrecord.ScreenRecordDialog"
+ android:theme="@style/ScreenRecord" />
+ <service android:name=".screenrecord.RecordingService" />
+
<receiver android:name=".SysuiRestartReceiver"
android:exported="false">
<intent-filter>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
new file mode 100644
index 000000000000..6c5c7fac3ed6
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="top"
+ android:orientation="vertical">
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="10dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/white">
+ <CheckBox
+ android:id="@+id/checkbox_mic"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/screenrecord_mic_label"/>
+ <CheckBox
+ android:id="@+id/checkbox_taps"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/screenrecord_taps_label"/>
+ <Button
+ android:id="@+id/record_button"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:text="@string/screenrecord_start_label"
+ />
+ </LinearLayout>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="10dp"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3e928a4227cd..d67841213c7e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -185,6 +185,39 @@
<string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
your organization</string>
+ <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
+ <string name="screenrecord_name">Screen Recording</string>
+ <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
+ <string name="screenrecord_channel_description">Ongoing notification for a screen record session</string>
+ <!-- Label for the button to begin screen recording [CHAR LIMIT=NONE]-->
+ <string name="screenrecord_start_label">Start Recording</string>
+ <!-- Label for the checkbox to enable microphone input during screen recording [CHAR LIMIT=NONE]-->
+ <string name="screenrecord_mic_label">Record voiceover</string>
+ <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]-->
+ <string name="screenrecord_taps_label">Show taps</string>
+ <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] -->
+ <string name="screenrecord_stop_label">Stop</string>
+ <!-- Label for notification action to pause screen recording [CHAR LIMIT=35] -->
+ <string name="screenrecord_pause_label">Pause</string>
+ <!-- Label for notification action to resume screen recording [CHAR LIMIT=35] -->
+ <string name="screenrecord_resume_label">Resume</string>
+ <!-- Label for notification action to cancel and discard screen recording [CHAR LIMIT=35] -->
+ <string name="screenrecord_cancel_label">Cancel</string>
+ <!-- Label for notification action to share screen recording [CHAR LIMIT=35] -->
+ <string name="screenrecord_share_label">Share</string>
+ <!-- Label for notification action to delete a screen recording file [CHAR LIMIT=35] -->
+ <string name="screenrecord_delete_label">Delete</string>
+ <!-- A toast message shown after successfully canceling a screen recording [CHAR LIMIT=NONE] -->
+ <string name="screenrecord_cancel_success">Screen recording canceled</string>
+ <!-- Notification text shown after saving a screen recording to prompt the user to view it [CHAR LIMIT=100] -->
+ <string name="screenrecord_save_message">Screen recording saved, tap to view</string>
+ <!-- A toast message shown after successfully deleting a screen recording [CHAR LIMIT=NONE] -->
+ <string name="screenrecord_delete_description">Screen recording deleted</string>
+ <!-- A toast message shown when there is an error deleting a screen recording [CHAR LIMIT=NONE] -->
+ <string name="screenrecord_delete_error">Error deleting screen recording</string>
+ <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] -->
+ <string name="screenrecord_permission_error">Failed to get permissions</string>
+
<!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
<string name="usb_preference_title">USB file transfer options</string>
<!-- Label for the MTP USB function in UsbPreferenceActivity. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6bc2a6352e3d..6244e1c4a509 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -503,4 +503,13 @@
<item name="chargingAnimColor">@android:color/white</item>
<item name="android:textColor">@android:color/white</item>
</style>
+
+ <!-- Screen recording -->
+ <style name="ScreenRecord" parent="Theme.SystemUI.Dialog.GlobalActions">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index 4aaa90fb5c53..fa6468fefe04 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -17,4 +17,5 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="leak" path="leak/"/>
+ <external-path name="screenrecord" path="."/>
</paths> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 512cd82731c2..dc7b1ef9a092 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -82,6 +82,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.util.ScreenRecordHelper;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
@@ -158,6 +159,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
private final ScreenshotHelper mScreenshotHelper;
+ private final ScreenRecordHelper mScreenRecordHelper;
/**
* @param context everything needs a context :(
@@ -199,6 +201,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
mScreenshotHelper = new ScreenshotHelper(context);
+ mScreenRecordHelper = new ScreenRecordHelper(context);
Dependency.get(ConfigurationController.class).addCallback(this);
}
@@ -522,7 +525,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
- private class ScreenshotAction extends SinglePressAction {
+ private class ScreenshotAction extends SinglePressAction implements LongPressAction {
public ScreenshotAction() {
super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
}
@@ -552,6 +555,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
public boolean showBeforeProvisioning() {
return false;
}
+
+ @Override
+ public boolean onLongPress() {
+ if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) {
+ mScreenRecordHelper.launchRecordPrompt();
+ } else {
+ onPress();
+ }
+ return true;
+ }
}
private class BugReportAction extends SinglePressAction implements LongPressAction {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
new file mode 100644
index 000000000000..a9896f51369c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaRecorder;
+import android.media.ThumbnailUtils;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Surface;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+
+import com.android.systemui.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A service which records the device screen and optionally microphone input.
+ */
+public class RecordingService extends Service {
+ private static final int NOTIFICATION_ID = 1;
+ private static final String TAG = "RecordingService";
+ private static final String CHANNEL_ID = "screen_record";
+ private static final String EXTRA_RESULT_CODE = "extra_resultCode";
+ private static final String EXTRA_DATA = "extra_data";
+ private static final String EXTRA_PATH = "extra_path";
+ private static final String EXTRA_USE_AUDIO = "extra_useAudio";
+ private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
+ private static final int REQUEST_CODE = 2;
+
+ private static final String ACTION_START = "com.android.systemui.screenrecord.START";
+ private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
+ private static final String ACTION_PAUSE = "com.android.systemui.screenrecord.PAUSE";
+ private static final String ACTION_RESUME = "com.android.systemui.screenrecord.RESUME";
+ private static final String ACTION_CANCEL = "com.android.systemui.screenrecord.CANCEL";
+ private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
+ private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
+
+ private static final int TOTAL_NUM_TRACKS = 1;
+ private static final String RECORD_DIR = "Captures"; // TODO: use a translatable string
+ private static final int VIDEO_BIT_RATE = 6000000;
+ private static final int VIDEO_FRAME_RATE = 30;
+ private static final int AUDIO_BIT_RATE = 16;
+ private static final int AUDIO_SAMPLE_RATE = 44100;
+ private static final String FILE_PROVIDER = "com.android.systemui.fileprovider";
+
+ private MediaProjectionManager mMediaProjectionManager;
+ private MediaProjection mMediaProjection;
+ private Surface mInputSurface;
+ private VirtualDisplay mVirtualDisplay;
+ private MediaRecorder mMediaRecorder;
+ private Notification.Builder mRecordingNotificationBuilder;
+
+ private boolean mUseAudio;
+ private boolean mShowTaps;
+ private File mTempFile;
+
+ /**
+ * Get an intent to start the recording service.
+ *
+ * @param context Context from the requesting activity
+ * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, int,
+ * android.content.Intent)}
+ * @param data The data from {@link android.app.Activity#onActivityResult(int, int,
+ * android.content.Intent)}
+ * @param useAudio True to enable microphone input while recording
+ * @param showTaps True to make touches visible while recording
+ */
+ public static Intent getStartIntent(Context context, int resultCode, Intent data,
+ boolean useAudio, boolean showTaps) {
+ return new Intent(context, RecordingService.class)
+ .setAction(ACTION_START)
+ .putExtra(EXTRA_RESULT_CODE, resultCode)
+ .putExtra(EXTRA_DATA, data)
+ .putExtra(EXTRA_USE_AUDIO, useAudio)
+ .putExtra(EXTRA_SHOW_TAPS, showTaps);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "RecordingService is starting");
+ if (intent == null) {
+ return Service.START_NOT_STICKY;
+ }
+ String action = intent.getAction();
+
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ switch (action) {
+ case ACTION_START:
+ int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED);
+ mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false);
+ mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
+ Intent data = intent.getParcelableExtra(EXTRA_DATA);
+ if (data != null) {
+ mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
+ startRecording();
+ }
+ break;
+
+ case ACTION_CANCEL:
+ stopRecording();
+
+ // Delete temp file
+ if (!mTempFile.delete()) {
+ Log.e(TAG, "Error canceling screen recording!");
+ Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
+ .show();
+ } else {
+ Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG)
+ .show();
+ }
+
+ // Close quick shade
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ break;
+
+ case ACTION_STOP:
+ stopRecording();
+
+ // Move temp file to user directory
+ File recordDir = new File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
+ RECORD_DIR);
+ recordDir.mkdirs();
+
+ String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
+ .format(new Date());
+ Path path = new File(recordDir, fileName).toPath();
+
+ try {
+ Files.move(mTempFile.toPath(), path);
+ Notification notification = createSaveNotification(path);
+ notificationManager.notify(NOTIFICATION_ID, notification);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
+ .show();
+ }
+ break;
+
+ case ACTION_PAUSE:
+ mMediaRecorder.pause();
+ setNotificationActions(true, notificationManager);
+ break;
+
+ case ACTION_RESUME:
+ mMediaRecorder.resume();
+ setNotificationActions(false, notificationManager);
+ break;
+
+ case ACTION_SHARE:
+ File shareFile = new File(intent.getStringExtra(EXTRA_PATH));
+ Uri shareUri = FileProvider.getUriForFile(this, FILE_PROVIDER, shareFile);
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND)
+ .setType("video/mp4")
+ .putExtra(Intent.EXTRA_STREAM, shareUri);
+ String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+
+ // Close quick shade
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ // Remove notification
+ notificationManager.cancel(NOTIFICATION_ID);
+
+ startActivity(Intent.createChooser(shareIntent, shareLabel)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ break;
+ case ACTION_DELETE:
+ // Close quick shade
+ sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ File file = new File(intent.getStringExtra(EXTRA_PATH));
+ if (file.delete()) {
+ Toast.makeText(
+ this,
+ R.string.screenrecord_delete_description,
+ Toast.LENGTH_LONG).show();
+
+ // Remove notification
+ notificationManager.cancel(NOTIFICATION_ID);
+ } else {
+ Log.e(TAG, "Error deleting screen recording!");
+ Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
+ .show();
+ }
+ break;
+ }
+ return Service.START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mMediaProjectionManager =
+ (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+ }
+
+ /**
+ * Begin the recording session
+ */
+ private void startRecording() {
+ try {
+ mTempFile = File.createTempFile("temp", ".mp4");
+ Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath());
+
+ setTapsVisible(mShowTaps);
+
+ // Set up media recorder
+ mMediaRecorder = new MediaRecorder();
+ if (mUseAudio) {
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ }
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+
+ // Set up video
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ int screenWidth = metrics.widthPixels;
+ int screenHeight = metrics.heightPixels;
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setVideoSize(screenWidth, screenHeight);
+ mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
+ mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
+
+ // Set up audio
+ if (mUseAudio) {
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+ mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS);
+ mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
+ mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
+ }
+
+ mMediaRecorder.setOutputFile(mTempFile);
+ mMediaRecorder.prepare();
+
+ // Create surface
+ mInputSurface = mMediaRecorder.getSurface();
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay(
+ "Recording Display",
+ screenWidth,
+ screenHeight,
+ metrics.densityDpi,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ mInputSurface,
+ null,
+ null);
+
+ mMediaRecorder.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+
+ createRecordingNotification();
+ }
+
+ private void createRecordingNotification() {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.screenrecord_name),
+ NotificationManager.IMPORTANCE_HIGH);
+ channel.setDescription(getString(R.string.screenrecord_channel_description));
+ channel.enableVibration(true);
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.createNotificationChannel(channel);
+
+ mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_android)
+ .setContentTitle(getResources().getString(R.string.screenrecord_name))
+ .setUsesChronometer(true)
+ .setOngoing(true);
+ setNotificationActions(false, notificationManager);
+ Notification notification = mRecordingNotificationBuilder.build();
+ startForeground(NOTIFICATION_ID, notification);
+ }
+
+ private void setNotificationActions(boolean isPaused, NotificationManager notificationManager) {
+ String pauseString = getResources()
+ .getString(isPaused ? R.string.screenrecord_resume_label
+ : R.string.screenrecord_pause_label);
+ Intent pauseIntent = isPaused ? getResumeIntent(this) : getPauseIntent(this);
+
+ mRecordingNotificationBuilder.setActions(
+ new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android),
+ getResources().getString(R.string.screenrecord_stop_label),
+ PendingIntent
+ .getService(this, REQUEST_CODE, getStopIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build(),
+ new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android), pauseString,
+ PendingIntent.getService(this, REQUEST_CODE, pauseIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build(),
+ new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android),
+ getResources().getString(R.string.screenrecord_cancel_label),
+ PendingIntent
+ .getService(this, REQUEST_CODE, getCancelIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build());
+ notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build());
+ }
+
+ private Notification createSaveNotification(Path path) {
+ Uri saveUri = FileProvider.getUriForFile(this, FILE_PROVIDER, path.toFile());
+ Log.d(TAG, "Screen recording saved to " + path.toString());
+
+ Intent viewIntent = new Intent(Intent.ACTION_VIEW)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .setDataAndType(saveUri, "video/mp4");
+
+ Notification.Action shareAction = new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android),
+ getResources().getString(R.string.screenrecord_share_label),
+ PendingIntent.getService(
+ this,
+ REQUEST_CODE,
+ getShareIntent(this, path.toString()),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build();
+
+ Notification.Action deleteAction = new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android),
+ getResources().getString(R.string.screenrecord_delete_label),
+ PendingIntent.getService(
+ this,
+ REQUEST_CODE,
+ getDeleteIntent(this, path.toString()),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build();
+
+ Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_android)
+ .setContentTitle(getResources().getString(R.string.screenrecord_name))
+ .setContentText(getResources().getString(R.string.screenrecord_save_message))
+ .setContentIntent(PendingIntent.getActivity(
+ this,
+ REQUEST_CODE,
+ viewIntent,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION))
+ .addAction(shareAction)
+ .addAction(deleteAction)
+ .setAutoCancel(true);
+
+ // Add thumbnail if available
+ Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(path.toString(),
+ MediaStore.Video.Thumbnails.MINI_KIND);
+ if (thumbnailBitmap != null) {
+ Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
+ .bigPicture(thumbnailBitmap)
+ .bigLargeIcon((Bitmap) null);
+ builder.setLargeIcon(thumbnailBitmap).setStyle(pictureStyle);
+ }
+ return builder.build();
+ }
+
+ private void stopRecording() {
+ setTapsVisible(false);
+ mMediaRecorder.stop();
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ mInputSurface.release();
+ mVirtualDisplay.release();
+ stopSelf();
+ }
+
+ private void setTapsVisible(boolean turnOn) {
+ int value = turnOn ? 1 : 0;
+ Settings.System.putInt(getApplicationContext().getContentResolver(),
+ Settings.System.SHOW_TOUCHES, value);
+ }
+
+ private static Intent getStopIntent(Context context) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
+ }
+
+ private static Intent getPauseIntent(Context context) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_PAUSE);
+ }
+
+ private static Intent getResumeIntent(Context context) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_RESUME);
+ }
+
+ private static Intent getCancelIntent(Context context) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_CANCEL);
+ }
+
+ private static Intent getShareIntent(Context context, String path) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
+ .putExtra(EXTRA_PATH, path);
+ }
+
+ private static Intent getDeleteIntent(Context context, String path) {
+ return new Intent(context, RecordingService.class).setAction(ACTION_DELETE)
+ .putExtra(EXTRA_PATH, path);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
new file mode 100644
index 000000000000..27e9fbab3161
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to select screen recording options
+ */
+public class ScreenRecordDialog extends Activity {
+ private static final String TAG = "ScreenRecord";
+ private static final int REQUEST_CODE_VIDEO_ONLY = 200;
+ private static final int REQUEST_CODE_VIDEO_TAPS = 201;
+ private static final int REQUEST_CODE_PERMISSIONS = 299;
+ private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
+ private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
+ private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
+ private boolean mUseAudio;
+ private boolean mShowTaps;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.screen_record_dialog);
+
+ final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
+ final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
+
+ final Button recordButton = findViewById(R.id.record_button);
+ recordButton.setOnClickListener(v -> {
+ mUseAudio = micCheckBox.isChecked();
+ mShowTaps = tapsCheckBox.isChecked();
+ Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
+
+ if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Requesting permission for audio");
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
+ REQUEST_CODE_PERMISSIONS_AUDIO);
+ } else {
+ requestScreenCapture();
+ }
+ });
+ }
+
+ private void requestScreenCapture() {
+ MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(
+ Context.MEDIA_PROJECTION_SERVICE);
+ Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
+
+ if (mUseAudio) {
+ startActivityForResult(permissionIntent,
+ mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+ } else {
+ startActivityForResult(permissionIntent,
+ mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+ switch (requestCode) {
+ case REQUEST_CODE_VIDEO_TAPS:
+ case REQUEST_CODE_VIDEO_AUDIO_TAPS:
+ case REQUEST_CODE_VIDEO_ONLY:
+ case REQUEST_CODE_VIDEO_AUDIO:
+ if (resultCode == RESULT_OK) {
+ mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
+ || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+ startForegroundService(
+ RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
+ mShowTaps));
+ } else {
+ Toast.makeText(this,
+ getResources().getString(R.string.screenrecord_permission_error),
+ Toast.LENGTH_SHORT).show();
+ }
+ finish();
+ break;
+ case REQUEST_CODE_PERMISSIONS:
+ int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (permission != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this,
+ getResources().getString(R.string.screenrecord_permission_error),
+ Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ requestScreenCapture();
+ }
+ break;
+ case REQUEST_CODE_PERMISSIONS_AUDIO:
+ int videoPermission = checkSelfPermission(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO);
+ if (videoPermission != PackageManager.PERMISSION_GRANTED
+ || audioPermission != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this,
+ getResources().getString(R.string.screenrecord_permission_error),
+ Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ requestScreenCapture();
+ }
+ break;
+ }
+ }
+}