Skip to content

Commit a4d0b29

Browse files
authored
Initial support for Android native screenshot functionality (#4105)
* Initial support for Android native screenshot functionality * Exposed screenshot public API and added iOS implementation
1 parent a1d9ce8 commit a4d0b29

File tree

8 files changed

+314
-108
lines changed

8 files changed

+314
-108
lines changed

CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,19 @@ public Image flipImageVertically(Image image, boolean maintainOpacity) {
12011201
return EncodedImage.createFromRGB(newRGB, width, height, !maintainOpacity);
12021202
}
12031203

1204+
/**
1205+
* Tries to grab an OS native screenshot which would include peer components etc.
1206+
* On fallback draws the current Form object.
1207+
*
1208+
* @param callback invoked with the screenshot
1209+
*/
1210+
public void screenshot(SuccessCallback<Image> callback) {
1211+
Form current = getCurrentForm();
1212+
Image img = Image.createImage(current.getWidth(), current.getHeight());
1213+
current.paintComponent(img.getGraphics(), true);
1214+
callback.onSucess(img);
1215+
}
1216+
12041217
/**
12051218
* Returns true if the platform supports a native image cache. The native image cache
12061219
* is different than just {@link FileSystemStorage#hasCachesDir()}. A native image cache
@@ -5686,6 +5699,7 @@ public void capturePhoto(ActionListener response) {
56865699
*
56875700
* @return An image of the screen, or null if it failed.
56885701
* @since 7.0
5702+
* @deprecated replaced by screenshot()
56895703
*/
56905704
public Image captureScreen() {
56915705
Form form = getCurrentForm();

CodenameOne/src/com/codename1/testing/TestUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ public static boolean screenshotTest(String screenshotName) {
584584
return true;
585585
}
586586

587-
Image mute = Image.createImage(Display.getInstance().getDisplayWidth(), Display.getInstance().getDisplayHeight());
587+
588+
Image mute = Display.getInstance().captureScreen();
588589
Display.getInstance().getCurrent().paintComponent(mute.getGraphics(), true);
589590
screenshotName = screenshotName + ".png";
590591
if (Storage.getInstance().exists(screenshotName)) {

CodenameOne/src/com/codename1/ui/Display.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import com.codename1.ui.util.ImageIO;
6363
import com.codename1.util.AsyncResource;
6464
import com.codename1.util.RunnableWithResultSync;
65+
import com.codename1.util.SuccessCallback;
6566

6667
import java.io.IOException;
6768
import java.io.InputStream;
@@ -4995,12 +4996,25 @@ public void onCanInstallOnHomescreen(Runnable r) {
49954996
*
49964997
* @return An image of the screen, or null if it failed.
49974998
* @since 7.0
4999+
* @deprecated use screenshot(SuccessCallback) instead
49985000
*/
49995001
public Image captureScreen() {
50005002
return impl.captureScreen();
50015003
}
50025004

50035005
/**
5006+
* Captures a screenshot in the native layer which should include peer
5007+
* components as well.
5008+
*
5009+
* @param callback will be invoked on the EDT with a screenshot
5010+
* @since 7.0.211
5011+
*/
5012+
public void screenshot(SuccessCallback<Image> callback) {
5013+
impl.screenshot(callback);
5014+
}
5015+
5016+
5017+
/**
50045018
* Convenience method to schedule a task to run on the EDT after {@literal timeout}ms.
50055019
*
50065020
* @param timeout The timeout in milliseconds.

Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java

Lines changed: 114 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@
5757
import android.util.Log;
5858
import android.util.TypedValue;
5959
import android.view.KeyEvent;
60-
import android.view.View;
61-
import android.view.ViewGroup;
62-
import android.view.Window;
60+
import android.view.View;
61+
import android.view.ViewGroup;
62+
import android.view.Window;
6363
import android.webkit.WebSettings;
6464
import android.webkit.WebView;
6565
import android.webkit.WebViewClient;
@@ -114,10 +114,10 @@
114114
import android.telephony.SmsManager;
115115
import android.telephony.gsm.GsmCellLocation;
116116
import android.text.Html;
117-
import android.view.*;
118-
import android.view.View.MeasureSpec;
119-
import android.view.accessibility.AccessibilityEvent;
120-
import android.view.accessibility.AccessibilityManager;
117+
import android.view.*;
118+
import android.view.View.MeasureSpec;
119+
import android.view.accessibility.AccessibilityEvent;
120+
import android.view.accessibility.AccessibilityManager;
121121
import android.webkit.*;
122122
import android.widget.*;
123123
import com.codename1.background.BackgroundFetch;
@@ -375,6 +375,13 @@ public static void stopContext(Context ctx) {
375375
}
376376
}
377377

378+
@Override
379+
public void screenshot(SuccessCallback<Image> callback) {
380+
final Activity activity = (Activity) getContext();
381+
final AndroidScreenshotTask task = new AndroidScreenshotTask(myView, activity, callback);
382+
activity.runOnUiThread(task);
383+
}
384+
378385
@Override
379386
public void setPlatformHint(String key, String value) {
380387
if(key.equals("platformHint.compatPaintMode")) {
@@ -8006,27 +8013,27 @@ private String getImageFilePath(Uri uri) {
80068013
//String[] filePaths = file.getPath().split(":");
80078014
//String image_id = filePath[filePath.length - 1];
80088015
String[] filePathColumn = {MediaStore.Images.Media.DATA};
8009-
Cursor cursor = getContext().getContentResolver().query(
8010-
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
8011-
new String[]{ MediaStore.Images.Media.DATA},
8012-
null,
8013-
null,
8014-
null
8015-
);
8016-
// Some gallery providers may return an empty cursor on modern Android builds.
8017-
String filePath = null;
8018-
if (cursor != null) {
8019-
try {
8020-
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
8021-
if (columnIndex >= 0 && cursor.moveToFirst()) {
8022-
filePath = cursor.getString(columnIndex);
8023-
}
8024-
} finally {
8025-
cursor.close();
8026-
}
8027-
}
8028-
8029-
if (filePath == null || "content".equals(scheme)) {
8016+
Cursor cursor = getContext().getContentResolver().query(
8017+
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
8018+
new String[]{ MediaStore.Images.Media.DATA},
8019+
null,
8020+
null,
8021+
null
8022+
);
8023+
// Some gallery providers may return an empty cursor on modern Android builds.
8024+
String filePath = null;
8025+
if (cursor != null) {
8026+
try {
8027+
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
8028+
if (columnIndex >= 0 && cursor.moveToFirst()) {
8029+
filePath = cursor.getString(columnIndex);
8030+
}
8031+
} finally {
8032+
cursor.close();
8033+
}
8034+
}
8035+
8036+
if (filePath == null || "content".equals(scheme)) {
80308037
//if the file is not on the filesystem download it and save it
80318038
//locally
80328039
try {
@@ -8167,26 +8174,26 @@ else if (requestCode == FILECHOOSER_RESULTCODE) {
81678174
Uri selectedImage = intent.getData();
81688175
String scheme = intent.getScheme();
81698176

8170-
String[] filePathColumn = {MediaStore.Images.Media.DATA};
8171-
Cursor cursor = getContext().getContentResolver().query(selectedImage, filePathColumn, null, null, null);
8172-
8173-
// Some gallery providers may return an empty cursor on modern Android builds.
8174-
String filePath = null;
8175-
if (cursor != null) {
8176-
try {
8177-
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
8178-
if (columnIndex >= 0 && cursor.moveToFirst()) {
8179-
filePath = cursor.getString(columnIndex);
8180-
}
8181-
} finally {
8182-
cursor.close();
8183-
}
8184-
}
8185-
boolean fileExists = false;
8186-
if (filePath != null) {
8187-
File file = new File(filePath);
8188-
fileExists = file.exists() && file.canRead();
8189-
}
8177+
String[] filePathColumn = {MediaStore.Images.Media.DATA};
8178+
Cursor cursor = getContext().getContentResolver().query(selectedImage, filePathColumn, null, null, null);
8179+
8180+
// Some gallery providers may return an empty cursor on modern Android builds.
8181+
String filePath = null;
8182+
if (cursor != null) {
8183+
try {
8184+
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
8185+
if (columnIndex >= 0 && cursor.moveToFirst()) {
8186+
filePath = cursor.getString(columnIndex);
8187+
}
8188+
} finally {
8189+
cursor.close();
8190+
}
8191+
}
8192+
boolean fileExists = false;
8193+
if (filePath != null) {
8194+
File file = new File(filePath);
8195+
fileExists = file.exists() && file.canRead();
8196+
}
81908197

81918198
if (!fileExists && "content".equals(scheme)) {
81928199
//if the file is not on the filesystem download it and save it
@@ -8214,23 +8221,23 @@ else if (requestCode == FILECHOOSER_RESULTCODE) {
82148221
}
82158222
}
82168223

8217-
if (filePath == null) {
8218-
callback.fireActionEvent(null);
8219-
return;
8220-
}
8221-
8222-
callback.fireActionEvent(new ActionEvent(new String[]{filePath}));
8223-
return;
8224+
if (filePath == null) {
8225+
callback.fireActionEvent(null);
8226+
return;
8227+
}
8228+
8229+
callback.fireActionEvent(new ActionEvent(new String[]{filePath}));
8230+
return;
82248231
} else if (requestCode == OPEN_GALLERY) {
82258232

82268233
Uri selectedImage = intent.getData();
82278234
String scheme = intent.getScheme();
82288235

8229-
String[] filePathColumn = {MediaStore.Images.Media.DATA};
8230-
Cursor cursor = getContext().getContentResolver().query(selectedImage, filePathColumn, null, null, null);
8231-
8232-
// Some gallery providers may return an empty cursor on modern Android builds.
8233-
String filePath = null;
8236+
String[] filePathColumn = {MediaStore.Images.Media.DATA};
8237+
Cursor cursor = getContext().getContentResolver().query(selectedImage, filePathColumn, null, null, null);
8238+
8239+
// Some gallery providers may return an empty cursor on modern Android builds.
8240+
String filePath = null;
82348241
if (cursor != null) {
82358242
try {
82368243
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
@@ -10818,50 +10825,50 @@ public void run() {
1081810825
return true;
1081910826
}
1082010827

10821-
public boolean isJailbrokenDevice() {
10822-
try {
10823-
Runtime.getRuntime().exec("su");
10824-
return true;
10825-
} catch(Throwable t) {
10826-
com.codename1.io.Log.e(t);
10827-
}
10828-
return false;
10829-
}
10830-
10831-
@Override
10832-
public void announceForAccessibility(final Component cmp, final String text) {
10833-
final Activity act = getActivity();
10834-
if (act == null) {
10835-
return;
10836-
}
10837-
act.runOnUiThread(new Runnable() {
10838-
@Override
10839-
public void run() {
10840-
View view = null;
10841-
if (cmp instanceof PeerComponent) {
10842-
Object peer = ((PeerComponent) cmp).getNativePeer();
10843-
if (peer instanceof View) {
10844-
view = (View) peer;
10845-
}
10846-
}
10847-
if (view == null) {
10848-
view = act.getWindow().getDecorView();
10849-
}
10850-
if (view == null) {
10851-
return;
10852-
}
10853-
if (Build.VERSION.SDK_INT >= 16) {
10854-
view.announceForAccessibility(text);
10855-
} else {
10856-
AccessibilityManager manager = (AccessibilityManager) act.getSystemService(Context.ACCESSIBILITY_SERVICE);
10857-
if (manager != null && manager.isEnabled()) {
10858-
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
10859-
event.getText().add(text);
10860-
event.setSource(view);
10861-
manager.sendAccessibilityEvent(event);
10862-
}
10863-
}
10864-
}
10865-
});
10866-
}
10867-
}
10828+
public boolean isJailbrokenDevice() {
10829+
try {
10830+
Runtime.getRuntime().exec("su");
10831+
return true;
10832+
} catch(Throwable t) {
10833+
com.codename1.io.Log.e(t);
10834+
}
10835+
return false;
10836+
}
10837+
10838+
@Override
10839+
public void announceForAccessibility(final Component cmp, final String text) {
10840+
final Activity act = getActivity();
10841+
if (act == null) {
10842+
return;
10843+
}
10844+
act.runOnUiThread(new Runnable() {
10845+
@Override
10846+
public void run() {
10847+
View view = null;
10848+
if (cmp instanceof PeerComponent) {
10849+
Object peer = ((PeerComponent) cmp).getNativePeer();
10850+
if (peer instanceof View) {
10851+
view = (View) peer;
10852+
}
10853+
}
10854+
if (view == null) {
10855+
view = act.getWindow().getDecorView();
10856+
}
10857+
if (view == null) {
10858+
return;
10859+
}
10860+
if (Build.VERSION.SDK_INT >= 16) {
10861+
view.announceForAccessibility(text);
10862+
} else {
10863+
AccessibilityManager manager = (AccessibilityManager) act.getSystemService(Context.ACCESSIBILITY_SERVICE);
10864+
if (manager != null && manager.isEnabled()) {
10865+
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
10866+
event.getText().add(text);
10867+
event.setSource(view);
10868+
manager.sendAccessibilityEvent(event);
10869+
}
10870+
}
10871+
}
10872+
});
10873+
}
10874+
}

0 commit comments

Comments
 (0)