Skip to content

Commit fe50bc1

Browse files
committed
demo of screenshot media projection APIs
1 parent 7b2f337 commit fe50bc1

File tree

27 files changed

+650
-0
lines changed

27 files changed

+650
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apply plugin: 'com.android.application'
2+
3+
dependencies {
4+
compile project(':webserver')
5+
}
6+
7+
android {
8+
compileSdkVersion 23
9+
buildToolsVersion "23.0.0"
10+
11+
defaultConfig {
12+
applicationId "com.commonsware.andprojector"
13+
minSdkVersion 21
14+
targetSdkVersion 23
15+
versionCode 1
16+
versionName "1.0"
17+
}
18+
19+
aaptOptions {
20+
noCompress 'html', 'js'
21+
}
22+
23+
buildTypes {
24+
release {
25+
minifyEnabled false
26+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27+
}
28+
}
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Add project specific ProGuard rules here.
2+
# By default, the flags in this file are appended to flags specified
3+
# in /opt/android-sdk-linux/tools/proguard/proguard-android.txt
4+
# You can edit the include path and order by changing the proguardFiles
5+
# directive in build.gradle.
6+
#
7+
# For more details, see
8+
# http://developer.android.com/guide/developing/tools/proguard.html
9+
10+
# Add any project specific keep options here:
11+
12+
# If your project uses WebView with JS, uncomment the following
13+
# and specify the fully qualified class name to the JavaScript interface
14+
# class:
15+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16+
# public *;
17+
#}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.commonsware.andprojector;
2+
3+
import android.app.Application;
4+
import android.test.ApplicationTestCase;
5+
6+
/**
7+
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
8+
*/
9+
public class ApplicationTest extends ApplicationTestCase<Application> {
10+
public ApplicationTest() {
11+
super(Application.class);
12+
}
13+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest
3+
package="com.commonsware.andprojector"
4+
xmlns:android="http://schemas.android.com/apk/res/android">
5+
6+
<uses-permission android:name="android.permission.INTERNET"/>
7+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
8+
9+
<application
10+
android:icon="@mipmap/ic_launcher"
11+
android:label="@string/app_name"
12+
android:theme="@style/AppTheme">
13+
<activity
14+
android:name=".MainActivity"
15+
android:theme="@style/AppTheme">
16+
<intent-filter>
17+
<action android:name="android.intent.action.MAIN"/>
18+
19+
<category android:name="android.intent.category.LAUNCHER"/>
20+
</intent-filter>
21+
</activity>
22+
23+
<service
24+
android:name=".ProjectorService"
25+
android:exported="false">
26+
<intent-filter>
27+
<action android:name="com.commonsware.android.webserver.WEB_SERVER_SERVICE"/>
28+
</intent-filter>
29+
</service>
30+
</application>
31+
32+
</manifest>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<html>
2+
<head>
3+
<title>andprojector</title>
4+
</head>
5+
<body>
6+
<img id="screen"
7+
style="height: 100%; width: 100%; object-fit: contain"
8+
src="screen/0">
9+
<script src="js/app.js"></script>
10+
</body>
11+
</html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
window.onload = function() {
2+
var screen=document.getElementById('screen');
3+
var ws_url=location.href.replace('http://', 'ws://')+'ss';
4+
var socket=new WebSocket(ws_url);
5+
6+
socket.onopen = function(event) {
7+
// console.log(event.currentTarget.url);
8+
};
9+
10+
socket.onerror = function(error) {
11+
console.log('WebSocket error: ' + error);
12+
};
13+
14+
socket.onmessage = function(event) {
15+
screen.src=event.data;
16+
};
17+
}

MediaProjection/andprojector/app/src/main/assets/js/jquery-1.11.3.min.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/***
2+
Copyright (c) 2015 CommonsWare, LLC
3+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
4+
use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
6+
by applicable law or agreed to in writing, software distributed under the
7+
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
8+
OF ANY KIND, either express or implied. See the License for the specific
9+
language governing permissions and limitations under the License.
10+
11+
From _The Busy Coder's Guide to Android Development_
12+
https://commonsware.com/Android
13+
*/
14+
15+
package com.commonsware.andprojector;
16+
17+
import android.graphics.Bitmap;
18+
import android.graphics.PixelFormat;
19+
import android.graphics.Point;
20+
import android.media.Image;
21+
import android.media.ImageReader;
22+
import android.view.Display;
23+
import android.view.Surface;
24+
import java.io.ByteArrayOutputStream;
25+
import java.nio.ByteBuffer;
26+
27+
public class ImageTransmogrifier implements ImageReader.OnImageAvailableListener {
28+
private final int width;
29+
private final int height;
30+
private final ImageReader imageReader;
31+
private final ProjectorService svc;
32+
private Bitmap latestBitmap=null;
33+
34+
ImageTransmogrifier(ProjectorService svc) {
35+
this.svc=svc;
36+
37+
Display display=svc.getWindowManager().getDefaultDisplay();
38+
Point size=new Point();
39+
40+
display.getSize(size);
41+
42+
int width=size.x;
43+
int height=size.y;
44+
45+
while (width*height > (2<<19)) {
46+
width=width>>1;
47+
height=height>>1;
48+
}
49+
50+
this.width=width;
51+
this.height=height;
52+
53+
imageReader=ImageReader.newInstance(width, height,
54+
PixelFormat.RGBA_8888, 2);
55+
imageReader.setOnImageAvailableListener(this, svc.getHandler());
56+
}
57+
58+
@Override
59+
public void onImageAvailable(ImageReader reader) {
60+
final Image image=imageReader.acquireLatestImage();
61+
62+
if (image!=null) {
63+
Image.Plane[] planes=image.getPlanes();
64+
ByteBuffer buffer=planes[0].getBuffer();
65+
int pixelStride=planes[0].getPixelStride();
66+
int rowStride=planes[0].getRowStride();
67+
int rowPadding=rowStride - pixelStride * width;
68+
int bitmapWidth=width + rowPadding / pixelStride;
69+
70+
if (latestBitmap == null ||
71+
latestBitmap.getWidth() != bitmapWidth ||
72+
latestBitmap.getHeight() != height) {
73+
if (latestBitmap != null) {
74+
latestBitmap.recycle();
75+
}
76+
77+
latestBitmap=Bitmap.createBitmap(bitmapWidth,
78+
height, Bitmap.Config.ARGB_8888);
79+
}
80+
81+
latestBitmap.copyPixelsFromBuffer(buffer);
82+
83+
if (image != null) {
84+
image.close();
85+
}
86+
87+
ByteArrayOutputStream baos=new ByteArrayOutputStream();
88+
Bitmap cropped=Bitmap.createBitmap(latestBitmap, 0, 0,
89+
width, height);
90+
91+
cropped.compress(Bitmap.CompressFormat.PNG, 100, baos);
92+
93+
byte[] newPng=baos.toByteArray();
94+
95+
svc.updateImage(newPng);
96+
}
97+
}
98+
99+
Surface getSurface() {
100+
return(imageReader.getSurface());
101+
}
102+
103+
int getWidth() {
104+
return(width);
105+
}
106+
107+
int getHeight() {
108+
return(height);
109+
}
110+
111+
void close() {
112+
imageReader.close();
113+
}
114+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/***
2+
Copyright (c) 2015 CommonsWare, LLC
3+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
4+
use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
6+
by applicable law or agreed to in writing, software distributed under the
7+
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
8+
OF ANY KIND, either express or implied. See the License for the specific
9+
language governing permissions and limitations under the License.
10+
11+
From _The Busy Coder's Guide to Android Development_
12+
https://commonsware.com/Android
13+
*/
14+
15+
package com.commonsware.andprojector;
16+
17+
import android.app.ListActivity;
18+
import android.content.Intent;
19+
import android.media.projection.MediaProjectionManager;
20+
import android.os.Bundle;
21+
import android.view.Menu;
22+
import android.view.MenuItem;
23+
import android.view.Window;
24+
import android.view.WindowManager;
25+
import android.widget.ArrayAdapter;
26+
import com.commonsware.android.webserver.WebServerService;
27+
import de.greenrobot.event.EventBus;
28+
29+
public class MainActivity extends ListActivity {
30+
private static final int REQUEST_SCREENSHOT=59706;
31+
private MenuItem start, stop;
32+
private MediaProjectionManager mgr;
33+
34+
@Override
35+
protected void onCreate(Bundle savedInstanceState) {
36+
super.onCreate(savedInstanceState);
37+
38+
Window window=getWindow();
39+
40+
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
41+
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
42+
window.setStatusBarColor(
43+
getResources().getColor(R.color.primary_dark));
44+
45+
mgr=(MediaProjectionManager)getSystemService(MEDIA_PROJECTION_SERVICE);
46+
}
47+
48+
@Override
49+
protected void onResume() {
50+
super.onResume();
51+
52+
EventBus.getDefault().registerSticky(this);
53+
}
54+
55+
@Override
56+
protected void onPause() {
57+
EventBus.getDefault().unregister(this);
58+
59+
super.onPause();
60+
}
61+
62+
@Override
63+
public boolean onCreateOptionsMenu(Menu menu) {
64+
getMenuInflater().inflate(R.menu.actions, menu);
65+
66+
start=menu.findItem(R.id.start);
67+
stop=menu.findItem(R.id.stop);
68+
69+
WebServerService.ServerStartedEvent event=
70+
EventBus.getDefault().getStickyEvent(WebServerService.ServerStartedEvent.class);
71+
72+
if (event!=null) {
73+
handleStartEvent(event);
74+
}
75+
76+
return(super.onCreateOptionsMenu(menu));
77+
}
78+
79+
@Override
80+
public boolean onOptionsItemSelected(MenuItem item) {
81+
if (item.getItemId()==R.id.start) {
82+
startActivityForResult(mgr.createScreenCaptureIntent(),
83+
REQUEST_SCREENSHOT);
84+
}
85+
else {
86+
stopService(new Intent(this, ProjectorService.class));
87+
}
88+
89+
return super.onOptionsItemSelected(item);
90+
}
91+
92+
@Override
93+
protected void onActivityResult(int requestCode, int resultCode,
94+
Intent data) {
95+
if (requestCode==REQUEST_SCREENSHOT) {
96+
if (resultCode==RESULT_OK) {
97+
Intent i=
98+
new Intent(this, ProjectorService.class)
99+
.putExtra(ProjectorService.EXTRA_RESULT_CODE,
100+
resultCode)
101+
.putExtra(ProjectorService.EXTRA_RESULT_INTENT,
102+
data);
103+
104+
startService(i);
105+
}
106+
}
107+
}
108+
109+
public void onEventMainThread(WebServerService.ServerStartedEvent event) {
110+
if (start!=null) {
111+
handleStartEvent(event);
112+
}
113+
}
114+
115+
public void onEventMainThread(WebServerService.ServerStoppedEvent event) {
116+
if (start!=null) {
117+
start.setVisible(true);
118+
stop.setVisible(false);
119+
setListAdapter(null);
120+
}
121+
}
122+
123+
private void handleStartEvent(WebServerService.ServerStartedEvent event) {
124+
start.setVisible(false);
125+
stop.setVisible(true);
126+
127+
setListAdapter(new ArrayAdapter<String>(this,
128+
android.R.layout.simple_list_item_1, event.getUrls()));
129+
}
130+
}

0 commit comments

Comments
 (0)