Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@ public static void acquirePushWakeLock(long timeout) {
}
}

private static Context context;
RelativeLayout relativeLayout;
private static Context context;
private static PermissionPromptCallback permissionPromptCallback;
RelativeLayout relativeLayout;
final Vector nativePeers = new Vector();
int lastDirectionalKeyEventReceivedByWrapper;
private EventDispatcher callback;
Expand Down Expand Up @@ -10929,9 +10930,36 @@ public boolean isGaussianBlurSupported() {
return (!brokenGaussian) && android.os.Build.VERSION.SDK_INT >= 11;
}

public static boolean checkForPermission(String permission, String description){
return checkForPermission(permission, description, false);
}
public static boolean checkForPermission(String permission, String description){
return checkForPermission(permission, description, false);
}

public static void setPermissionPromptCallback(PermissionPromptCallback callback) {
permissionPromptCallback = callback;
}

public static PermissionPromptCallback getPermissionPromptCallback() {
return permissionPromptCallback;
}

private static String getPermissionText(String key, String defaultValue) {
return UIManager.getInstance().localize(key, Display.getInstance().getProperty(key, defaultValue));
}

private static boolean showPermissionPrompt(String permission, String title, String body, String positiveButtonText, String negativeButtonText) {
if (permissionPromptCallback != null) {
return permissionPromptCallback.showPermissionPrompt(permission, title, body, positiveButtonText, negativeButtonText);
}
return Dialog.show(title, body, positiveButtonText, negativeButtonText);
}

private static void showPermissionMessage(String permission, String title, String body, String okButtonText) {
if (permissionPromptCallback != null) {
permissionPromptCallback.showPermissionMessage(permission, title, body, okButtonText);
return;
}
Dialog.show(title, body, okButtonText, null);
}

/**
* Return a list of all of the permissions that have been requested by the app (granted or no).
Expand Down Expand Up @@ -10971,29 +10999,29 @@ public static boolean checkForPermission(String permission, String description,
return false;
}

String prompt = Display.getInstance().getProperty(permission, description);
String title = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.title", "Requires permission");
String settingsBtn = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.settings", "Settings");
String cancelBtn = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.cancel", "Cancel");
if(Dialog.show(title, prompt, settingsBtn, cancelBtn)){
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getContext().getPackageName(), null);
intent.setData(uri);
getActivity().startActivity(intent);
String explanationTitle = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.explanation_title", "Permission Required");
String explanationBody = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.explanation_body", "Please enable 'Allow all the time' in the settings, then press OK.");
String okBtn = Display.getInstance().getProperty("android.permission.ACCESS_BACKGROUND_LOCATION.ok", "OK");
Dialog.show(explanationTitle, explanationBody, okBtn, null);
return android.support.v4.content.ContextCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED;
} else {
return false;
}
}
String prompt = Display.getInstance().getProperty(permission, description);
String prompt = getPermissionText(permission, description);
String title = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.title", "Requires permission");
String settingsBtn = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.settings", "Settings");
String cancelBtn = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.cancel", "Cancel");

if(showPermissionPrompt(permission, title, prompt, settingsBtn, cancelBtn)){
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getContext().getPackageName(), null);
intent.setData(uri);
getActivity().startActivity(intent);

String explanationTitle = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.explanation_title", "Permission Required");
String explanationBody = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.explanation_body", "Please enable 'Allow all the time' in the settings, then press OK.");
String okBtn = getPermissionText("android.permission.ACCESS_BACKGROUND_LOCATION.ok", "OK");

showPermissionMessage(permission, explanationTitle, explanationBody, okBtn);
return android.support.v4.content.ContextCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED;
} else {
return false;
}
}

String prompt = getPermissionText(permission, description);

if (android.support.v4.content.ContextCompat.checkSelfPermission(getContext(),
permission)
Expand All @@ -11008,11 +11036,14 @@ public static boolean checkForPermission(String permission, String description,
permission)) {

// Show an expanation to the user *asynchronously* -- don't block
if(Dialog.show("Requires permission", prompt, "Ask again", "Don't Ask")){
return checkForPermission(permission, description, true);
}else {
return false;
}
String title = getPermissionText(permission + ".title", "Requires permission");
String askAgain = getPermissionText(permission + ".askAgain", "Ask again");
String dontAsk = getPermissionText(permission + ".dontAsk", "Don't Ask");
if(showPermissionPrompt(permission, title, prompt, askAgain, dontAsk)){
return checkForPermission(permission, description, true);
}else {
return false;
}
} else {

// No explanation needed, we can request the permission.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,19 @@ public static void registerViewRenderer(Class viewClass, BitmapViewRenderer b) {
viewRendererMap.put(viewClass, b);
}

public static interface BitmapViewRenderer {
public Bitmap renderViewOnBitmap(View v, int w, int h);
}
public static interface BitmapViewRenderer {
public Bitmap renderViewOnBitmap(View v, int w, int h);
}

/**
* Sets a callback used to customize permission rationale dialogs.
* Passing {@code null} restores the default Codename One Dialog behavior.
*
* @param callback callback implementation or {@code null}
*/
public static void setPermissionPromptCallback(final PermissionPromptCallback callback) {
AndroidImplementation.setPermissionPromptCallback(callback);
}

/**
* Check for a dangerous permission, if the permission is already granted return true,
Expand All @@ -208,7 +218,7 @@ public static interface BitmapViewRenderer {
* @param description show a description to the user why this is needed
* @return true if granted false otherwise
*/
public static boolean checkForPermission(String permission, String description){
return AndroidImplementation.checkForPermission(permission, description, false);
}
}
public static boolean checkForPermission(String permission, String description){
return AndroidImplementation.checkForPermission(permission, description, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.impl.android;

/**
* Callback used to customize Android permission rationale dialogs.
*/
public interface PermissionPromptCallback {
/**
* Shows a permission prompt that has positive and negative actions.
*
* @param permission Android permission name
* @param title prompt title
* @param body prompt body text
* @param positiveButtonText positive button text
* @param negativeButtonText negative button text
* @return true to perform the positive action, false for the negative action
*/
public boolean showPermissionPrompt(String permission, String title, String body, String positiveButtonText, String negativeButtonText);

/**
* Shows an informational permission message with only one action button.
*
* @param permission Android permission name
* @param title dialog title
* @param body dialog body
* @param okButtonText button text
*/
public void showPermissionMessage(String permission, String title, String body, String okButtonText);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.list.MultiButton;
import com.codename1.impl.android.AndroidNativeUtil;
import com.codename1.impl.android.PermissionPromptCallback;

/**
* Snippets related to Android runtime permissions.
Expand Down Expand Up @@ -52,4 +53,33 @@ public void checkForPermission() {
// you have the permission, do what you need
// end::androidCheckForPermission[]
}


public void customizePermissionPromptLocalization() {
// tag::permissionPromptLocalization[]
com.codename1.ui.plaf.UIManager.getInstance().setBundle(new java.util.Hashtable<String, String>() {{
put("android.permission.READ_CONTACTS", "Localized rationale for contacts");
put("android.permission.READ_CONTACTS.title", "Localized permission title");
put("android.permission.READ_CONTACTS.askAgain", "Localized ask again");
put("android.permission.READ_CONTACTS.dontAsk", "Localized don't ask");
}});
// end::permissionPromptLocalization[]
}

public void installNativePermissionPromptCallback() {
// tag::androidPermissionPromptCallback[]
AndroidNativeUtil.setPermissionPromptCallback(new PermissionPromptCallback() {
@Override
public boolean showPermissionPrompt(String permission, String title, String body, String positiveButtonText, String negativeButtonText) {
return com.codename1.ui.Dialog.show(title, body, positiveButtonText, negativeButtonText);
}

@Override
public void showPermissionMessage(String permission, String title, String body, String okButtonText) {
com.codename1.ui.Dialog.show(title, body, okButtonText, null);
}
});
// end::androidPermissionPromptCallback[]
}

}
40 changes: 31 additions & 9 deletions docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -563,22 +563,35 @@ There are no explicit code changes needed for this functionality to "just work".

TIP: Some behaviors that never occurred on Android but were perfectly legal in the past might start occurring with the switch to the new API. E.g. the location manager might be null and your app must always be ready to deal with such a situation

When permission is requested a user will be seamlessly prompted/warned, Codename One has builtin text to control such prompts but you might want to customize the text. You can customize permission text via the `Display` properties e.g. to customize the text of the contacts permission we can do something such as:
When permission is requested a user will be seamlessly prompted/warned. You can customize the permission text via `Display` properties. E.g. to customize the rationale text of the contacts permission:

[source,java]
----
include::../demos/android/src/main/java/com/codenameone/developerguide/advancedtopics/PermissionSnippets.java[tag=permissionPrompt,indent=0]
----

This is optional as there is a default value defined. You can define this once in the `init(Object)` method but for some extreme cases permission might be needed for different things e.g. you might ask for this permission with one reason at one point in the app and with a different reason at another point in the app.
The Android port also checks `UIManager.localize()` for localized values before falling back to `Display` properties. You can provide localized strings for the permission body and dialog buttons/title using keys based on the permission name:

[source,java]
----
include::../demos/android/src/main/java/com/codenameone/developerguide/advancedtopics/PermissionSnippets.java[tag=permissionPromptLocalization,indent=0]
----

For example, if the permission key is `android.permission.READ_CONTACTS`, you can localize these keys:

The following permission keys are supported: "android.permission.READ_PHONE_STATE"
`android.permission.WRITE_EXTERNAL_STORAGE`,
`android.permission.ACCESS_FINE_LOCATION`,
`android.permission.SEND_SMS`,
`android.permission.READ_CONTACTS`,
`android.permission.WRITE_CONTACTS`,
`android.permission.RECORD_AUDIO`.
* `android.permission.READ_CONTACTS` (dialog body)
* `android.permission.READ_CONTACTS.title`
* `android.permission.READ_CONTACTS.askAgain`
* `android.permission.READ_CONTACTS.dontAsk`

The same pattern applies to other permissions. `android.permission.ACCESS_BACKGROUND_LOCATION` also supports:
`android.permission.ACCESS_BACKGROUND_LOCATION.settings`,
`android.permission.ACCESS_BACKGROUND_LOCATION.cancel`,
`android.permission.ACCESS_BACKGROUND_LOCATION.explanation_title`,
`android.permission.ACCESS_BACKGROUND_LOCATION.explanation_body`, and
`android.permission.ACCESS_BACKGROUND_LOCATION.ok`.

This is optional as there is a default value defined. You can define this once in the `init(Object)` method but for some extreme cases permission might be needed for different things e.g. you might ask for this permission with one reason at one point in the app and with a different reason at another point in the app.

===== Simulating Prompts

Expand All @@ -605,6 +618,15 @@ include::../demos/android/src/main/java/com/codenameone/developerguide/advancedt

This will prompt the user with the native UI and later on with the fallback option as described above. Notice that the `checkForPermission` method is a blocking method and it will return when there is a final conclusion on the subject. It uses `invokeAndBlock` and can be safely invoked on the event dispatch thread without concern.

By default, fallback prompts are displayed using Codename One's `Dialog.show(...)`. If you are writing native Android code and need to use your own prompt implementation, you can install a custom callback:

[source,java]
----
include::../demos/android/src/main/java/com/codenameone/developerguide/advancedtopics/PermissionSnippets.java[tag=androidPermissionPromptCallback,indent=0]
----

Pass `null` to `AndroidNativeUtil.setPermissionPromptCallback()` to restore the default behavior.

=== On Device Debugging

Codename One supports debugging applications on devices by using the natively generated project. All paid subscription levels include the ability to check an #Include Source# flag in the settings that returns a native OS project. You can debug that project in the respective native IDE.
Expand Down
Loading