summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKoushik Dutta <koushd@gmail.com>2013-09-15 17:50:31 -0700
committerRicardo Cerqueira <ricardo@cyngn.com>2015-11-12 13:45:43 +0000
commit5e8da3e0efcf7f314a2b2230c3897f881a12ed25 (patch)
treea1b247006e7231ee1d1523141566f06bb68f297b
downloadandroid_packages_apps_Screencast-5e8da3e0efcf7f314a2b2230c3897f881a12ed25.tar.gz
android_packages_apps_Screencast-5e8da3e0efcf7f314a2b2230c3897f881a12ed25.tar.bz2
android_packages_apps_Screencast-5e8da3e0efcf7f314a2b2230c3897f881a12ed25.zip
This is a combination of 54 commits.
First pass at Mirror tweaking random numbers xda style. clone ipad fixup fps info consolidate header add server for testing. What is this blob? Is it a transformation matrix? Great success! All framing issues fixed. wip ntp timing issues resolved. document code Proper cleanup on devices nsd manager fixups, use preresolved address quick hack fixes for danesh fix fixes assumptions that position/offset are 0. and one more spot Make use of framework hooks for start/stop recordings Update device name Check available space before recording move screencast call into service. add getListener call to display manager reenable test activity nre fix refactor for new framework changes add hidden device support add share and show touches add notifications create the dir before create the file my brain meats are on fire... device to device mirroring fix up surface to scale to fit airplay now advertises proper txt records. retrieve the airplay sink width/height. fixes everything seems smooth now. not sure if because of the ntp code tweaks or because i turned off everything in my house. add airplay header use localized string cleanup remove some derp screen recording now has mic input wip cleanups autotimeout nsd manager discovery nre fix not_enough_storage support mirroring to a browser via flash. rename to new package rename module disable flash device make it build friendly remove redundant write Updates for CM11 cm-11.0 fixes Add content intent to recording notification. Use new/better assets for notification. Strip out all the derp, keep screencasting rename further renames. launcher icon. Change-Id: I2bdec5d9b45aa912fe0c8101dbaf331cca06e1d0
-rw-r--r--.gitignore8
-rw-r--r--Android.mk17
-rw-r--r--AndroidManifest.xml51
-rw-r--r--proguard.flags12
-rw-r--r--res/drawable-hdpi-v11/ic_stat_device_access_video.pngbin0 -> 378 bytes
-rw-r--r--res/drawable-hdpi-v11/ic_stat_rating_important.pngbin0 -> 775 bytes
-rw-r--r--res/drawable-hdpi-v11/ic_stat_rating_not_important.pngbin0 -> 999 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin0 -> 643 bytes
-rw-r--r--res/drawable-hdpi/ic_stat_device_access_video.pngbin0 -> 446 bytes
-rw-r--r--res/drawable-hdpi/ic_stat_rating_important.pngbin0 -> 756 bytes
-rw-r--r--res/drawable-hdpi/ic_stat_rating_not_important.pngbin0 -> 928 bytes
-rwxr-xr-xres/drawable-hdpi/stop.pngbin0 -> 1055 bytes
-rw-r--r--res/drawable-mdpi-v11/ic_stat_device_access_video.pngbin0 -> 261 bytes
-rw-r--r--res/drawable-mdpi-v11/ic_stat_rating_important.pngbin0 -> 507 bytes
-rw-r--r--res/drawable-mdpi-v11/ic_stat_rating_not_important.pngbin0 -> 616 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin0 -> 471 bytes
-rw-r--r--res/drawable-mdpi/ic_stat_device_access_video.pngbin0 -> 327 bytes
-rw-r--r--res/drawable-mdpi/ic_stat_rating_important.pngbin0 -> 517 bytes
-rw-r--r--res/drawable-mdpi/ic_stat_rating_not_important.pngbin0 -> 552 bytes
-rwxr-xr-xres/drawable-mdpi/stop.pngbin0 -> 1047 bytes
-rw-r--r--res/drawable-xhdpi-v11/ic_stat_device_access_video.pngbin0 -> 481 bytes
-rw-r--r--res/drawable-xhdpi-v11/ic_stat_rating_important.pngbin0 -> 1053 bytes
-rw-r--r--res/drawable-xhdpi-v11/ic_stat_rating_not_important.pngbin0 -> 1435 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin0 -> 923 bytes
-rw-r--r--res/drawable-xhdpi/ic_stat_device_access_video.pngbin0 -> 561 bytes
-rw-r--r--res/drawable-xhdpi/ic_stat_rating_important.pngbin0 -> 1006 bytes
-rw-r--r--res/drawable-xhdpi/ic_stat_rating_not_important.pngbin0 -> 1260 bytes
-rwxr-xr-xres/drawable-xhdpi/stop.pngbin0 -> 1114 bytes
-rw-r--r--res/drawable-xxhdpi-v11/ic_stat_device_access_video.pngbin0 -> 708 bytes
-rw-r--r--res/drawable-xxhdpi-v11/ic_stat_rating_important.pngbin0 -> 1667 bytes
-rw-r--r--res/drawable-xxhdpi-v11/ic_stat_rating_not_important.pngbin0 -> 2414 bytes
-rw-r--r--res/drawable-xxhdpi/ic_launcher.pngbin0 -> 1473 bytes
-rw-r--r--res/drawable-xxhdpi/ic_stat_device_access_video.pngbin0 -> 834 bytes
-rw-r--r--res/drawable-xxhdpi/ic_stat_rating_important.pngbin0 -> 1612 bytes
-rw-r--r--res/drawable-xxhdpi/ic_stat_rating_not_important.pngbin0 -> 2127 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_launcher.pngbin0 -> 2127 bytes
-rw-r--r--res/layout/main.xml23
-rw-r--r--res/values/strings.xml14
-rw-r--r--src/com/cyanogenmod/screencast/EncoderDevice.java151
-rw-r--r--src/com/cyanogenmod/screencast/MainActivity.java40
-rw-r--r--src/com/cyanogenmod/screencast/RecordingDevice.java279
-rw-r--r--src/com/cyanogenmod/screencast/ScreencastControlReceiver.java12
-rw-r--r--src/com/cyanogenmod/screencast/ScreencastService.java247
-rw-r--r--src/com/cyanogenmod/screencast/ScreencastStartReceiver.java28
44 files changed, 882 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8f295e2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+bin
+.settings
+local.properties
+gen
+.gradle
+build
+.DS_Store
+*.apk
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..1441bec
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES += $(call all-java-files-under, src)
+
+# remove the dns sd txt record class which allows android studio to build, but
+# causes build errors in CM
+LOCAL_SRC_FILES := $(filter-out src/android/net/nsd/DnsSdTxtRecord.java, $(LOCAL_SRC_FILES))
+
+LOCAL_PACKAGE_NAME := Mirror
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..478d5c6
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.cyanogenmod.screencast"
+ android:versionCode="1000"
+ android:versionName="1.0.0.0"
+ android:sharedUserId="com.cyanogenmod.uid.cms">
+ <uses-sdk android:minSdkVersion="19"/>
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
+ <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" />
+
+ <application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
+ <service android:name=".ScreencastService"
+ android:permission="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+ android:label="@string/app_name" android:exported="false">
+ <intent-filter>
+ <action android:name="com.cyanogenmod.server.display.SCAN" />
+ </intent-filter>
+ </service>
+ <activity
+ android:theme="@android:style/Theme.Holo.Dialog.NoActionBar"
+ android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <receiver android:name=".ScreencastStartReceiver" android:permission="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT">
+ <intent-filter>
+ <action android:name="com.cyanogenmod.ACTION_START_SCREENCAST" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".ScreencastControlReceiver">
+ <intent-filter>
+ <action android:name="com.cyanogenmod.ACTION_STOP_SCREENCAST"/>
+ <action android:name="com.cyanogenmod.SHOW_TOUCHES"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..0501c9b
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,12 @@
+# Disable the warnings of using dynamic method call in common library.
+-keep class * extends com.koushikdutta.async.TapCallback {
+ public protected private *;
+}
+
+-keep class android.net.nsd.DnsSdTxtRecord {
+ public protected private *;
+}
+
+-keep class android.net.nsd.NsdServiceInfo {
+ public protected private *;
+} \ No newline at end of file
diff --git a/res/drawable-hdpi-v11/ic_stat_device_access_video.png b/res/drawable-hdpi-v11/ic_stat_device_access_video.png
new file mode 100644
index 0000000..9072a22
--- /dev/null
+++ b/res/drawable-hdpi-v11/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-hdpi-v11/ic_stat_rating_important.png b/res/drawable-hdpi-v11/ic_stat_rating_important.png
new file mode 100644
index 0000000..d46097b
--- /dev/null
+++ b/res/drawable-hdpi-v11/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-hdpi-v11/ic_stat_rating_not_important.png b/res/drawable-hdpi-v11/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..4f7e777
--- /dev/null
+++ b/res/drawable-hdpi-v11/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cea7e1f
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_stat_device_access_video.png b/res/drawable-hdpi/ic_stat_device_access_video.png
new file mode 100644
index 0000000..ad36a69
--- /dev/null
+++ b/res/drawable-hdpi/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_stat_rating_important.png b/res/drawable-hdpi/ic_stat_rating_important.png
new file mode 100644
index 0000000..d448954
--- /dev/null
+++ b/res/drawable-hdpi/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_stat_rating_not_important.png b/res/drawable-hdpi/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..1c85ccf
--- /dev/null
+++ b/res/drawable-hdpi/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-hdpi/stop.png b/res/drawable-hdpi/stop.png
new file mode 100755
index 0000000..dd5d6a1
--- /dev/null
+++ b/res/drawable-hdpi/stop.png
Binary files differ
diff --git a/res/drawable-mdpi-v11/ic_stat_device_access_video.png b/res/drawable-mdpi-v11/ic_stat_device_access_video.png
new file mode 100644
index 0000000..257eb48
--- /dev/null
+++ b/res/drawable-mdpi-v11/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-mdpi-v11/ic_stat_rating_important.png b/res/drawable-mdpi-v11/ic_stat_rating_important.png
new file mode 100644
index 0000000..be0ca8e
--- /dev/null
+++ b/res/drawable-mdpi-v11/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-mdpi-v11/ic_stat_rating_not_important.png b/res/drawable-mdpi-v11/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..7c93d11
--- /dev/null
+++ b/res/drawable-mdpi-v11/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..4fb5155
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_stat_device_access_video.png b/res/drawable-mdpi/ic_stat_device_access_video.png
new file mode 100644
index 0000000..0fde26d
--- /dev/null
+++ b/res/drawable-mdpi/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_stat_rating_important.png b/res/drawable-mdpi/ic_stat_rating_important.png
new file mode 100644
index 0000000..eea8483
--- /dev/null
+++ b/res/drawable-mdpi/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_stat_rating_not_important.png b/res/drawable-mdpi/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..22623ba
--- /dev/null
+++ b/res/drawable-mdpi/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-mdpi/stop.png b/res/drawable-mdpi/stop.png
new file mode 100755
index 0000000..20df415
--- /dev/null
+++ b/res/drawable-mdpi/stop.png
Binary files differ
diff --git a/res/drawable-xhdpi-v11/ic_stat_device_access_video.png b/res/drawable-xhdpi-v11/ic_stat_device_access_video.png
new file mode 100644
index 0000000..147c486
--- /dev/null
+++ b/res/drawable-xhdpi-v11/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-xhdpi-v11/ic_stat_rating_important.png b/res/drawable-xhdpi-v11/ic_stat_rating_important.png
new file mode 100644
index 0000000..118eca8
--- /dev/null
+++ b/res/drawable-xhdpi-v11/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-xhdpi-v11/ic_stat_rating_not_important.png b/res/drawable-xhdpi-v11/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..9969646
--- /dev/null
+++ b/res/drawable-xhdpi-v11/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..0db71fc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_stat_device_access_video.png b/res/drawable-xhdpi/ic_stat_device_access_video.png
new file mode 100644
index 0000000..fe65cd5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_stat_rating_important.png b/res/drawable-xhdpi/ic_stat_rating_important.png
new file mode 100644
index 0000000..1ce2e9c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_stat_rating_not_important.png b/res/drawable-xhdpi/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..76a914c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-xhdpi/stop.png b/res/drawable-xhdpi/stop.png
new file mode 100755
index 0000000..ee5eda2
--- /dev/null
+++ b/res/drawable-xhdpi/stop.png
Binary files differ
diff --git a/res/drawable-xxhdpi-v11/ic_stat_device_access_video.png b/res/drawable-xxhdpi-v11/ic_stat_device_access_video.png
new file mode 100644
index 0000000..b87a54e
--- /dev/null
+++ b/res/drawable-xxhdpi-v11/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-xxhdpi-v11/ic_stat_rating_important.png b/res/drawable-xxhdpi-v11/ic_stat_rating_important.png
new file mode 100644
index 0000000..a39b081
--- /dev/null
+++ b/res/drawable-xxhdpi-v11/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-xxhdpi-v11/ic_stat_rating_not_important.png b/res/drawable-xxhdpi-v11/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..6b7cd33
--- /dev/null
+++ b/res/drawable-xxhdpi-v11/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..95e1dcd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_stat_device_access_video.png b/res/drawable-xxhdpi/ic_stat_device_access_video.png
new file mode 100644
index 0000000..c23919b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_stat_device_access_video.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_stat_rating_important.png b/res/drawable-xxhdpi/ic_stat_rating_important.png
new file mode 100644
index 0000000..afdeb26
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_stat_rating_important.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_stat_rating_not_important.png b/res/drawable-xxhdpi/ic_stat_rating_not_important.png
new file mode 100644
index 0000000..3fa1183
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_stat_rating_not_important.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher.png b/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..72bae7d
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644
index 0000000..77a5ad5
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_margin="20dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@android:style/TextAppearance.Medium"
+ android:gravity="center"
+ android:text="@string/start_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:id="@+id/start_screencast"
+ android:layout_marginTop="20dp"
+ android:text="@string/start_screencast"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..ababc01
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Screencast</string>
+ <string name="share">Share</string>
+ <string name="stop">Stop</string>
+ <string name="insufficient_storage">Insufficient Storage</string>
+ <string name="show_touches">Show Touches</string>
+ <string name="recording">Recording</string>
+ <string name="recording_ready_to_share">Recording ready to share</string>
+ <string name="video_length">Video Length: %s</string>
+ <string name="not_enough_storage">Not enough storage available</string>
+ <string name="start_screencast">Start Screencast</string>
+ <string name="start_description">Pretty "Start Screencast" to begin recording your Android screen and microphone to a video file.</string>
+</resources>
diff --git a/src/com/cyanogenmod/screencast/EncoderDevice.java b/src/com/cyanogenmod/screencast/EncoderDevice.java
new file mode 100644
index 0000000..0502f51
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/EncoderDevice.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013 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.cyanogenmod.screencast;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+import android.view.Surface;
+
+public abstract class EncoderDevice {
+ final String LOGTAG = getClass().getSimpleName();
+ private MediaCodec venc;
+ int width;
+ int height;
+ private VirtualDisplay virtualDisplay;
+
+ public VirtualDisplay registerVirtualDisplay(Context context, String name, int width, int height, int densityDpi) {
+ assert virtualDisplay == null;
+ DisplayManager dm = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ Surface surface = createDisplaySurface();
+ if (surface == null)
+ return null;
+ return virtualDisplay = dm.createVirtualDisplay(name, width, height, 1, surface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE);
+ }
+
+ public EncoderDevice(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ public void stop() {
+ if (venc != null) {
+ try {
+ venc.signalEndOfInputStream();
+ }
+ catch (Exception e) {
+ }
+ venc = null;
+ }
+ if (virtualDisplay != null) {
+ virtualDisplay.release();
+ virtualDisplay = null;
+ }
+ }
+
+ private void destroyDisplaySurface(MediaCodec venc) {
+ if (venc == null)
+ return;
+ // release this surface
+ try {
+ venc.stop();
+ venc.release();
+ }
+ catch (Exception e) {
+ }
+ // see if this device is still in use
+ if (this.venc != venc)
+ return;
+ // display is done, kill it
+ this.venc = null;
+
+ if (virtualDisplay != null) {
+ virtualDisplay.release();
+ virtualDisplay = null;
+ }
+ }
+
+ abstract class EncoderRunnable implements Runnable {
+ MediaCodec venc;
+ public EncoderRunnable(MediaCodec venc) {
+ this.venc = venc;
+ }
+
+ abstract void encode() throws Exception;
+ protected void cleanup() {
+ destroyDisplaySurface(venc);
+ venc = null;
+ }
+
+ @Override
+ final public void run() {
+ try {
+ encode();
+ }
+ catch (Exception e) {
+ Log.e(LOGTAG, "Encoder error", e);
+ }
+ cleanup();
+ Log.i(LOGTAG, "=======ENCODING COMPELTE=======");
+ }
+ }
+
+ protected abstract EncoderRunnable onSurfaceCreated(MediaCodec venc);
+
+ public final Surface createDisplaySurface() {
+ if (venc != null) {
+ // signal any old crap to end
+ try {
+ venc.signalEndOfInputStream();
+ }
+ catch (Exception e) {
+ }
+ venc = null;
+ }
+
+ // TODO: choose proper bit rate and width/height
+ MediaFormat video = MediaFormat.createVideoFormat("video/avc", width, height);
+ int bitrate;
+ if (width >= 1080 || height >= 1080)
+ bitrate = 4000000;
+ else
+ bitrate = 2000000;
+
+ bitrate = 6000000;
+
+ video.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+
+ video.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ video.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ video.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
+
+ // create a surface from the encoder
+ Log.i(LOGTAG, "Starting encoder");
+ venc = MediaCodec.createEncoderByType("video/avc");
+ venc.configure(video, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ Surface surface = venc.createInputSurface();
+ venc.start();
+
+ EncoderRunnable runnable = onSurfaceCreated(venc);
+ new Thread(runnable, "Encoder").start();
+ return surface;
+ }
+}
diff --git a/src/com/cyanogenmod/screencast/MainActivity.java b/src/com/cyanogenmod/screencast/MainActivity.java
new file mode 100644
index 0000000..0f6d6bf
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/MainActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.cyanogenmod.screencast;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class MainActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ findViewById(R.id.start_screencast)
+ .setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startService(new Intent("com.cyanogenmod.ACTION_START_SCREENCAST")
+ .setClass(MainActivity.this, ScreencastService.class));
+ finish();
+ }
+ });
+ }
+}
diff --git a/src/com/cyanogenmod/screencast/RecordingDevice.java b/src/com/cyanogenmod/screencast/RecordingDevice.java
new file mode 100644
index 0000000..d7ef439
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/RecordingDevice.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2013 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.cyanogenmod.screencast;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.MediaRecorder;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.Semaphore;
+
+class RecordingDevice extends EncoderDevice {
+ private static final String LOGTAG = "RecordingDevice";
+ private static final File RECORDINGS_DIR = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "Screencasts");
+ File path;
+ Context context;
+
+ public RecordingDevice(Context context, int width, int height) {
+ super(width, height);
+ this.context = context;
+ // Prepare all the output metadata
+ String videoDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(System.currentTimeMillis()));
+ // the directory which holds all recording files
+ path = new File(RECORDINGS_DIR, "Screencast_" + videoDate + ".mp4");
+ }
+
+ /**
+ * @return the path of the screen cast file.
+ */
+ public String getRecordingFilePath() {
+ return path.getAbsolutePath();
+ }
+
+ @Override
+ protected EncoderRunnable onSurfaceCreated(MediaCodec venc) {
+ return new Recorder(venc);
+ }
+
+ // thread to mux the encoded audio into the final mp4 file
+ class AudioMuxer implements Runnable {
+ AudioRecorder audio;
+ MediaMuxer muxer;
+ int track;
+ // the video encoder waits for the audio muxer to get ready with
+ // this semaphore
+ Semaphore muxWaiter;
+ AudioMuxer(AudioRecorder audio, MediaMuxer muxer, Semaphore muxWaiter) {
+ this.audio = audio;
+ this.muxer = muxer;
+ this.muxWaiter = muxWaiter;
+ }
+
+ @Override
+ public void run() {
+ try {
+ encode();
+ }
+ catch (Exception e) {
+ Log.e(LOGTAG, "Audio Muxer error", e);
+ }
+ Log.i(LOGTAG, "AudioMuxer done");
+ muxWaiter.release();
+ }
+
+ void encode() throws Exception {
+ ByteBuffer[] outs = audio.codec.getOutputBuffers();
+ boolean doneCoding = false;
+ // used to rewrite the presentation timestamps into something 0 based
+ long start = System.nanoTime();
+ while (!doneCoding) {
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int bufIndex = audio.codec.dequeueOutputBuffer(info, -1);
+ if (bufIndex >= 0) {
+ ByteBuffer b = outs[bufIndex];
+
+ info.presentationTimeUs = (System.nanoTime() - start) / 1000L;
+ muxer.writeSampleData(track, b, info);
+
+ audio.codec.releaseOutputBuffer(bufIndex, false);
+ doneCoding = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ }
+ else if (bufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ outs = audio.codec.getOutputBuffers();
+ }
+ else if (bufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat newFormat = audio.codec.getOutputFormat();
+ track = muxer.addTrack(newFormat);
+ muxer.start();
+ muxWaiter.release();
+ }
+ }
+ }
+ }
+
+ // Start up an AudioRecord thread to record the mic, and feed
+ // the data to an encoder.
+ class AudioRecorder implements Runnable {
+ Recorder recorder;
+ AudioRecord record;
+ MediaCodec codec;
+ MediaFormat format;
+ public AudioRecorder(Recorder recorder) {
+ codec = MediaCodec.createEncoderByType("audio/mp4a-latm");
+ format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
+ format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);
+ format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
+ format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE);
+ codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ codec.start();
+
+ this.recorder = recorder;
+
+ int bufferSize = 1024 * 30;
+ int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
+ if (bufferSize < minBufferSize)
+ bufferSize = ((minBufferSize / 1024) + 1) * 1024 * 2;
+ Log.i(LOGTAG, "AudioRecorder init");
+ record = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
+ }
+
+ @Override
+ public void run() {
+ try {
+ Log.i(LOGTAG, "AudioRecorder start");
+ record.startRecording();
+ encode();
+ }
+ catch (Exception e) {
+ Log.e(LOGTAG, "AudioRecorder error", e);
+ }
+ Log.i(LOGTAG, "AudioRecorder done");
+ try {
+ record.stop();
+ }
+ catch (Exception e) {
+ }
+ try {
+ record.release();
+ }
+ catch (Exception e) {
+ }
+ }
+
+ void encode() throws Exception {
+ ByteBuffer[] inputs = codec.getInputBuffers();
+ while (!recorder.doneCoding) {
+ int bufIndex = codec.dequeueInputBuffer(1024);
+ if (bufIndex < 0)
+ continue;
+ ByteBuffer b = inputs[bufIndex];
+ b.clear();
+ int size = record.read(b, b.capacity());
+ size = size < 0 ? 0 : size;
+ b.clear();
+ codec.queueInputBuffer(bufIndex, 0, size, System.nanoTime() / 1000L, 0);
+ }
+ int bufIndex = codec.dequeueInputBuffer(-1);
+ codec.queueInputBuffer(bufIndex, 0, 0, System.nanoTime() / 1000L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ }
+ }
+
+ class Recorder extends EncoderRunnable {
+ public Recorder(MediaCodec venc) {
+ super(venc);
+ }
+ boolean doneCoding = false;
+
+ @Override
+ protected void cleanup() {
+ super.cleanup();
+ doneCoding = true;
+ }
+
+ @Override
+ public void encode() throws Exception {
+ path.getParentFile().mkdirs();
+ MediaMuxer muxer = new MediaMuxer(path.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ boolean muxerStarted = false;
+ int trackIndex = -1;
+ Thread audioThread = null;
+ AudioMuxer audioMuxer;
+ AudioRecorder audio;
+
+ ByteBuffer[] encouts = venc.getOutputBuffers();
+ long start = System.nanoTime();
+ while (!doneCoding) {
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int bufIndex = venc.dequeueOutputBuffer(info, -1);
+ if (bufIndex >= 0) {
+ Log.i(LOGTAG, "Dequeued buffer " + info.presentationTimeUs);
+
+ if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ // The codec config data was pulled out and fed to the muxer when we got
+ // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
+ Log.d(LOGTAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
+ info.size = 0;
+ }
+
+ if (!muxerStarted) {
+ throw new RuntimeException("muxer hasn't started");
+ }
+
+ ByteBuffer b = encouts[bufIndex];
+
+ info.presentationTimeUs = (System.nanoTime() - start) / 1000L;
+ muxer.writeSampleData(trackIndex, b, info);
+
+ b.clear();
+ venc.releaseOutputBuffer(bufIndex, false);
+
+ doneCoding = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ }
+ else if (bufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ encouts = venc.getOutputBuffers();
+ }
+ else if (bufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ // should happen before receiving buffers, and should only happen once
+ if (muxerStarted) {
+ throw new RuntimeException("format changed twice");
+ }
+ MediaFormat newFormat = venc.getOutputFormat();
+ Log.d(LOGTAG, "encoder output format changed: " + newFormat);
+
+ // now that we have the Magic Goodies, start the muxer
+ trackIndex = muxer.addTrack(newFormat);
+ audio = new AudioRecorder(this);
+ Semaphore semaphore = new Semaphore(0);
+ audioMuxer = new AudioMuxer(audio, muxer, semaphore);
+ muxerStarted = true;
+ new Thread(audio, "AudioRecorder").start();
+ audioThread = new Thread(audioMuxer, "AudioMuxer");
+ audioThread.start();
+
+ semaphore.acquire();
+ Log.i(LOGTAG, "Muxing");
+ }
+ }
+ doneCoding = true;
+ Log.i(LOGTAG, "Done recording");
+ audioThread.join();
+ muxer.stop();
+ MediaScannerConnection.scanFile(context,
+ new String[]{path.getAbsolutePath()}, null,
+ new MediaScannerConnection.OnScanCompletedListener() {
+ public void onScanCompleted(String path, Uri uri) {
+ Log.i(LOGTAG, "MediaScanner scanned recording " + path);
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/screencast/ScreencastControlReceiver.java b/src/com/cyanogenmod/screencast/ScreencastControlReceiver.java
new file mode 100644
index 0000000..21067a4
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/ScreencastControlReceiver.java
@@ -0,0 +1,12 @@
+package com.cyanogenmod.screencast;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class ScreencastControlReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.startService(intent.setClass(context, ScreencastService.class));
+ }
+}
diff --git a/src/com/cyanogenmod/screencast/ScreencastService.java b/src/com/cyanogenmod/screencast/ScreencastService.java
new file mode 100644
index 0000000..935973e
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/ScreencastService.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2013 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.cyanogenmod.screencast;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.StatFs;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.widget.Toast;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class ScreencastService extends Service {
+ private static final String LOGTAG = "ScreencastService";
+ public static final String SCREENCASTER_NAME = "hidden:screen-recording";
+ private long startTime;
+ private Timer timer;
+ private Notification.Builder mBuilder;
+ RecordingDevice recorder;
+
+ private static final String SHOW_TOUCHES = "show_touches";
+ private boolean wasShowingTouches;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ void cleanup() {
+ if (recorder != null) {
+ recorder.stop();
+ sendShareNotification();
+ recorder = null;
+ }
+ if (timer != null) {
+ timer.cancel();
+ timer = null;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ cleanup();
+ NotificationManager notificationManager =
+ (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ super.onDestroy();
+ }
+
+ private boolean hasAvailableSpace() {
+ StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
+ long bytesAvailable = stat.getBlockSizeLong() * stat.getBlockCountLong();
+ long megAvailable = bytesAvailable / 1048576;
+ return megAvailable >= 100;
+ }
+
+ public void updateNotification(Context context) {
+ long timeElapsed = System.currentTimeMillis() - startTime;
+ SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
+ mBuilder.setContentText("Video Length : " + sdf.format(new Date(timeElapsed)));
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(0, mBuilder.build());
+ }
+
+ void registerScreencaster() throws RemoteException {
+ DisplayManager dm = (DisplayManager)getSystemService(DISPLAY_SERVICE);
+ Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+
+ assert recorder == null;
+ recorder = new RecordingDevice(this, metrics.widthPixels, metrics.heightPixels);
+ VirtualDisplay vd = recorder.registerVirtualDisplay(this, SCREENCASTER_NAME, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
+ if (vd == null)
+ cleanup();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && "com.cyanogenmod.server.display.SCAN".equals(intent.getAction())) {
+// registerFlashDevice();
+ return START_STICKY;
+ }
+ else if (intent != null && "com.cyanogenmod.server.display.STOP_SCAN".equals(intent.getAction())) {
+ return START_STICKY;
+ }
+ else if (intent != null && TextUtils.equals(intent.getAction(), "com.cyanogenmod.ACTION_START_SCREENCAST")) {
+ try {
+ if (!hasAvailableSpace()) {
+ Toast.makeText(this, R.string.not_enough_storage, Toast.LENGTH_LONG).show();
+ return START_STICKY;
+ }
+ DisplayManager displayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ Point size = new Point();
+ display.getSize(size);
+ startTime = System.currentTimeMillis();
+ registerScreencaster();
+ mBuilder = createNotificationBuilder();
+ // record the initial state of show_touches,
+ // if show_touches is not enabled, give user the option to enable it
+ SharedPreferences sp = getSharedPreferences(AIRPLAY_PREFS_NAME,
+ Context.MODE_PRIVATE);
+ if (Settings.System.getInt(getContentResolver(), SHOW_TOUCHES, 0) == 0) {
+ sp.edit().putInt(SHOW_TOUCHES_INIT, 0);
+ Intent showTouches = new Intent("com.cyanogenmod.SHOW_TOUCHES");
+ showTouches.putExtra(SHOW_TOUCHES, "on");
+ mBuilder.addAction(android.R.drawable.checkbox_off_background, "show touches", PendingIntent.getBroadcast(this, 0, showTouches, PendingIntent.FLAG_UPDATE_CURRENT));
+ } else {
+ sp.edit().putInt(SHOW_TOUCHES_INIT, 1);
+ }
+ timer = new Timer();
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ updateNotification(ScreencastService.this);
+ }
+ }, 100, 1000);
+ }
+ catch (Exception e) {
+ Log.e("Mirror", "error", e);
+ }
+ }
+ else if (intent != null && TextUtils.equals(intent.getAction(), "com.cyanogenmod.ACTION_STOP_SCREENCAST")) {
+ try {
+ // clean show_touches settings if user enable show_touches in this activity
+ Settings.System.putInt(getContentResolver(), SHOW_TOUCHES, wasShowingTouches ? 1 : 0);
+ if (!hasAvailableSpace()) {
+ Toast.makeText(this, "Not enough storage space available", Toast.LENGTH_LONG).show();
+ return START_STICKY;
+ }
+ cleanup();
+ }
+ catch (Exception e) {
+ Log.e("Mirror", "error", e);
+ }
+ } else if (intent != null && intent.getAction().equals("com.cyanogenmod.SHOW_TOUCHES")) {
+ String showTouchesValue = intent.getStringExtra(SHOW_TOUCHES);
+ Intent showTouchesIntent = new Intent("com.cyanogenmod.SHOW_TOUCHES");
+ mBuilder = createNotificationBuilder();
+ if ("on".equals(showTouchesValue)) {
+ Settings.System.putInt(getContentResolver(), SHOW_TOUCHES, 1);
+ showTouchesIntent.putExtra(SHOW_TOUCHES, "off");
+ mBuilder.addAction(android.R.drawable.checkbox_on_background, "show touches", PendingIntent.getBroadcast(this, 0, showTouchesIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+ } else {
+ Settings.System.putInt(getContentResolver(), SHOW_TOUCHES, 0);
+ showTouchesIntent.putExtra(SHOW_TOUCHES, "on");
+ mBuilder.addAction(android.R.drawable.checkbox_off_background, "show touches", PendingIntent.getBroadcast(this, 0, showTouchesIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+ }
+ }
+ return START_STICKY;
+ }
+
+ private void addNotificationTouchButton(boolean showingTouches) {
+ Intent showTouchesIntent = new Intent("com.cyanogenmod.SHOW_TOUCHES");
+ if (showingTouches) {
+ Settings.System.putInt(getContentResolver(), SHOW_TOUCHES, 1);
+ showTouchesIntent.putExtra(SHOW_TOUCHES, "off");
+ mBuilder.addAction(R.drawable.ic_stat_rating_important, getString(R.string.show_touches), PendingIntent.getBroadcast(this, 0, showTouchesIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+ } else {
+ Settings.System.putInt(getContentResolver(), SHOW_TOUCHES, 0);
+ showTouchesIntent.putExtra(SHOW_TOUCHES, "on");
+ mBuilder.addAction(R.drawable.ic_stat_rating_not_important, getString(R.string.show_touches), PendingIntent.getBroadcast(this, 0, showTouchesIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+ }
+ }
+
+ private Notification.Builder createNotificationBuilder() {
+ Notification.Builder builder = new Notification.Builder(this)
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.ic_stat_device_access_video)
+ .setContentTitle(getString(R.string.recording));
+ Intent stopRecording = new Intent("com.cyanogenmod.ACTION_STOP_SCREENCAST");
+ builder.addAction(R.drawable.stop, "Stop Recording", PendingIntent.getBroadcast(this, 0, stopRecording, 0));
+ return builder;
+ }
+
+ private void sendShareNotification() {
+ if (recorder == null) return;
+ String recordingFilePath = recorder.getRecordingFilePath();
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ // share the screencast file
+ mBuilder = createShareNotificationBuilder(recordingFilePath);
+ notificationManager.notify(0, mBuilder.build());
+ }
+
+ private Notification.Builder createShareNotificationBuilder(String file) {
+ Intent sharingIntent = new Intent(Intent.ACTION_SEND);
+ sharingIntent.setType("video/mp4");
+ Uri uri = Uri.parse("file://" + file);
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, file);
+ Intent chooserIntent = Intent.createChooser(sharingIntent, null);
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ long timeElapsed = System.currentTimeMillis() - startTime;
+ SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
+
+ Log.i(LOGTAG, "Video complete: " + uri);
+
+ Intent open = new Intent(Intent.ACTION_VIEW);
+ open.setDataAndType(uri, "video/mp4");
+ open.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ Notification.Builder builder = new Notification.Builder(this)
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(R.drawable.ic_stat_device_access_video)
+ .setContentTitle(getString(R.string.recording_ready_to_share))
+ .setContentText(getString(R.string.video_length, sdf.format(new Date(timeElapsed))))
+ .addAction(android.R.drawable.ic_menu_share, getString(R.string.share),
+ PendingIntent.getActivity(this, 0, chooserIntent, PendingIntent.FLAG_CANCEL_CURRENT))
+ .setContentIntent(contentIntent);
+ return builder;
+ }
+}
diff --git a/src/com/cyanogenmod/screencast/ScreencastStartReceiver.java b/src/com/cyanogenmod/screencast/ScreencastStartReceiver.java
new file mode 100644
index 0000000..9de575c
--- /dev/null
+++ b/src/com/cyanogenmod/screencast/ScreencastStartReceiver.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 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.cyanogenmod.screencast;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class ScreencastStartReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.startService(intent.setClass(context, ScreencastService.class));
+ }
+}