Implementing Android 6.0 permissions in unity3d - android

I've installed Android Support Library but in the developer.android site says that for implementing it on my project I need to edit my build.gradle file that I don't have because it's an Unity project.
I've created a build.gradle file copying the content of this website: http://gradleplease.appspot.com/ and I put that file on the root of my Unity project but when I try to use the library it does not work
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}

You need Java code to ask for permission, and you need an interface into said Java code from Unity's C# runtime. You need to create a Unity Plugin to do this.
Below is the plugin that I've created to grant the WRITE_EXTERNAL_STORAGE permission at runtime.
You need a project structure like this:
Plugins/
Android/
NoodlePermissionGranter/
project.properties
AndroidManifest.xml
NoodlePermissionGranter.cs
libs/
NoodlePermissionGranter.jar
NoodlePermissionGranter.cs:
///////////////////////////////////////////////////////////
///////////////// NoodlePermissionGranter /////////////////
/// Implements runtime granting of Android permissions. ///
/// This is necessary for Android M (6.0) and above. //////
///////////////////////////////////////////////////////////
//////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
using System;
public class NoodlePermissionGranter : MonoBehaviour {
// subscribe to this callback to see if your permission was granted.
public static Action<bool> PermissionRequestCallback;
// for now, it only implements the external storage permission
public enum NoodleAndroidPermission
{
WRITE_EXTERNAL_STORAGE
}
public static void GrantPermission(NoodleAndroidPermission permission)
{
if (!initialized)
initialize ();
noodlePermissionGranterClass.CallStatic ("grantPermission", activity, (int)permission);
}
//////////////////////////////
/// Initialization Stuff /////
//////////////////////////////
// it's a singleton, but no one needs to know about it. hush hush. dont touch me.
private static NoodlePermissionGranter instance;
private static bool initialized = false;
public void Awake()
{
// instance is also set in initialize.
// having it here ensures this thing doesnt break
// if you added this component to the scene manually
instance = this;
DontDestroyOnLoad (this.gameObject);
// object name must match UnitySendMessage call in NoodlePermissionGranter.java
if (name != NOODLE_PERMISSION_GRANTER)
name = NOODLE_PERMISSION_GRANTER;
}
private static void initialize()
{
// runs once when you call GrantPermission
// add object to scene
if (instance == null) {
GameObject go = new GameObject();
// instance will also be set in awake, but having it here as well seems extra safe
instance = go.AddComponent<NoodlePermissionGranter>();
// object name must match UnitySendMessage call in NoodlePermissionGranter.java
go.name = NOODLE_PERMISSION_GRANTER;
}
// get the jni stuff. we need the activty class and the NoodlePermissionGranter class.
noodlePermissionGranterClass = new AndroidJavaClass("com.noodlecake.unityplugins.NoodlePermissionGranter");
AndroidJavaClass u3d = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
activity = u3d.GetStatic<AndroidJavaObject> ("currentActivity");
initialized = true;
}
///////////////////
//// JNI Stuff ////
///////////////////
static AndroidJavaClass noodlePermissionGranterClass;
static AndroidJavaObject activity;
private const string WRITE_EXTERNAL_STORAGE="WRITE_EXTERNAL_STORAGE";
private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match NoodlePermissionGranter.java
private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match NoodlePermissionGranter.java
private const string NOODLE_PERMISSION_GRANTER = "NoodlePermissionGranter"; // must match UnitySendMessage call in NoodlePermissionGranter.java
private void permissionRequestCallbackInternal(string message)
{
// were calling this method from the java side.
// the method name and gameobject must match NoodlePermissionGranter.java's UnitySendMessage
bool permissionGranted = (message == PERMISSION_GRANTED);
if (PermissionRequestCallback != null)
PermissionRequestCallback (permissionGranted);
}
}
NoodlePermissionGranter.java:
package com.noodlecake.unityplugins;
///////////////////////////////////////////////////////////
///////////////// NoodlePermissionGranter /////////////////
/// Implements runtime granting of Android permissions. ///
/// This is necessary for Android M (6.0) and above. //////
///////////////////////////////////////////////////////////
//////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////
import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;
import java.io.File;
import com.unity3d.player.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;
public class NoodlePermissionGranter
{
// Only implements WRITE_EXTERNAL_STORAGE so far.
// Implement the rest by matching the enum in NoodlePermissionGranter.cs
// to the getPermissionStringFromEnumInt below.
private final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "NoodlePermissionGranter";
private final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
private final static String PERMISSION_GRANTED = "PERMISSION_GRANTED"; // this will be an arg to the above method
private final static String PERMISSION_DENIED = "PERMISSION_DENIED";
public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
{
switch (permissionEnum)
{
case 0:
return Manifest.permission.WRITE_EXTERNAL_STORAGE;
// "and the rest is still unwritten" - Natasha Bedingfield
}
Log.e("NoodlePermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
}
public static void grantPermission(Activity currentActivity, int permissionEnum)
{
// permission enum must match ordering in NoodlePermissionGranter.cs
final Activity act = currentActivity;
Log.i("NoodlePermissionGranter","grantPermission " + permissionEnum) ;
if (Build.VERSION.SDK_INT < 23) {
Log.i("NoodlePermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
return;
}
try
{
final int PERMISSIONS_REQUEST_CODE = permissionEnum;
final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
if (currentActivity.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
Log.i("NoodlePermissionGranter", "already granted");
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
return;
}
final FragmentManager fragmentManager = currentActivity.getFragmentManager();
final Fragment request = new Fragment() {
#Override public void onStart()
{
super.onStart();
Log.i("NoodlePermissionGranter","fragment start");
String[] permissionsToRequest = new String [] {permissionFromEnumInt};
Log.i("NoodlePermissionGranter","fragment start " + permissionsToRequest[0]);
requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
}
#Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
Log.i("NoodlePermissionGranter", "onRequestPermissionsResult");
if (requestCode != PERMISSIONS_REQUEST_CODE)
return;
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
Log.i("NoodlePermissionGranter", PERMISSION_GRANTED);
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Log.i("NoodlePermissionGranter",PERMISSION_DENIED);
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(this);
fragmentTransaction.commit();
// shouldBeOkayToStartTheApplicationNow();
}
};
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(0, request);
fragmentTransaction.commit();
}
catch(Exception error)
{
Log.w("[NoodlePermissionGranter]", String.format("Unable to request permission: %s", error.getMessage()));
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
}
}
}
BuildNoodlePermissionGranter.sh
export JAVA_HOME=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
ClASSPATH=$UNITY_ROOT"/Unity.app/Contents/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar"
javac NoodlePermissionGranter.java -bootclasspath $ANDROID_SDK_ROOT/platforms/android-23/android.jar -classpath $ClASSPATH -d .
javap -s com.noodlecake.unityplugins.NoodlePermissionGranter
jar cvfM NoodlePermissionGranter.jar com/
rm -rf com
You need project.properties and a dummy AndroidManifest.xml in order to have Unity package a jar outside of Plugins/Android/libs
project.properties
target=android-9
android.library=true
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.noodlecake.unityplugins.noodlepermissiongranter"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:targetSdkVersion="23" />
</manifest>
It'd be nice if the PermissionRequestCallback supplied the requested permission enum as a parameter, but UnityPlayer.UnitySendMessage only supports a single string argument and I decided not to implemented the string serialization (using JSON to do this would be a good choice).

Another addition to Jason's excellent code for Unity 5.3.3 and up (I'm using 5.4), I added this to the manifest to block Unity from automatically asking at launch:
<application>
<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
</application>

In addition to Jason Knight's post (which I used for my own Unity plugin for handling runtime permissions):
I used Android studio for creating a plugin. I followed the directions on the following site and worked perfectly: http://www.thegamecontriver.com/2015/04/android-plugin-unity-android-studio.html
I also added another method using the shouldShowRequestPermissionRationale() function so I'm able to hide certain UI elements if the user denied the permission and checked the "Don't ask again" checkbox.

The other answers (especially Jason Knight's) have been super helpful to me, but I had to adjust the code to get it to work, so I'm sharing those changes here.
As noted in the comments, that code has this error in Android Studio: final Fragment request = new Fragment(); part saying "Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static"
Now I'm not a Java expert so maybe I did things wrong, but I tried to adjust things as explained here: Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static
Mostly, I split the Fragment into a new class, so that it's not an anonymous class. So now there are two Java files:
package com.synapse.unityplugins;
import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;
import com.unity3d.player.UnityPlayer;
public class PermissionGranter {
public final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "SynapsePlugin_listener";
public final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
public final static String PERMISSION_GRANTED = "PERMISSION_GRANTED";
public final static String PERMISSION_DENIED = "PERMISSION_DENIED";
// only implemented WRITE_EXTERNAL_STORAGE so far
public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
{
switch (permissionEnum) {
case 0:
return Manifest.permission.WRITE_EXTERNAL_STORAGE;
// "and the rest is still unwritten" - Natasha Bedingfield
}
Log.e("PermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
}
public static void grantPermission(int permissionEnum)
{
final Activity act = UnityPlayer.currentActivity;
Log.i("PermissionGranter","grantPermission " + permissionEnum) ;
if (Build.VERSION.SDK_INT < 23) {
Log.i("PermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
return;
}
try {
final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
if (act.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
Log.i("PermissionGranter", "already granted");
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
return;
}
final Fragment request = PermissionRequestFragment.newInstance(permissionEnum);
final FragmentManager fragmentManager = act.getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(0, request);
fragmentTransaction.commit();
}
catch(Exception error)
{
Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
}
}
}
That's the main plugin class, and here's the fragment:
package com.synapse.unityplugins;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
public class PermissionRequestFragment extends Fragment {
public static PermissionRequestFragment newInstance(int permissionEnum) {
PermissionRequestFragment frag = new PermissionRequestFragment();
Bundle args = new Bundle();
args.putInt("requested", permissionEnum);
frag.setArguments(args);
return frag;
}
#Override
public void onStart() {
super.onStart();
int permissionEnum = getArguments().getInt("requested");
final int PERMISSIONS_REQUEST_CODE = permissionEnum;
try {
final String permissionFromEnumInt = PermissionGranter.getPermissionStringFromEnumInt(permissionEnum);
String[] permissionsToRequest = new String[]{permissionFromEnumInt};
Log.i("PermissionGranter", "fragment start " + permissionsToRequest[0]);
requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
} catch (Exception error) {
Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
Log.i("PermissionGranter", "onRequestPermissionsResult");
int permissionEnum = getArguments().getInt("requested");
final int PERMISSIONS_REQUEST_CODE = permissionEnum;
if (requestCode != PERMISSIONS_REQUEST_CODE)
return;
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the task now
Log.i("PermissionGranter", PermissionGranter.PERMISSION_GRANTED);
UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_GRANTED);
} else {
// permission denied, boo! Disable the functionality that needed it
Log.i("PermissionGranter", PermissionGranter.PERMISSION_DENIED);
UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
}
final Activity act = UnityPlayer.currentActivity;
final FragmentManager fragmentManager = act.getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(this);
fragmentTransaction.commit();
}
}
And for completeness here's the C# within Unity:
using UnityEngine;
using System.Collections;
using System;
public class SynapsePlugin : MonoBehaviour {
// subscribe to this callback to see if your permission was granted.
public static Action<bool> PermissionRequestCallback;
// for now, it only implements the external storage permission
public enum AndroidPermission {
WRITE_EXTERNAL_STORAGE
}
public static void GrantPermission(AndroidPermission permission) {
if (!initialized)
initialize ();
PermissionGranterClass.CallStatic ("grantPermission", (int)permission);
}
//////////////////////////////
/// Initialization Stuff /////
//////////////////////////////
private const string PLUGIN_LISTENER_NAME = "SynapsePlugin_listener"; // must match UnitySendMessage call in Java
// it's a singleton, but no one needs to know about it. hush hush. dont touch me.
private static SynapsePlugin instance;
private static bool initialized = false;
static AndroidJavaClass PermissionGranterClass;
private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match Java
private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match Java
// runs automatically when making calls, or can pre-init manually
public static void initialize() {
// add object to scene
if (instance == null) {
GameObject go = new GameObject();
go.name = PLUGIN_LISTENER_NAME;
// instance will also be set in awake, but having it here as well seems extra safe
instance = go.AddComponent<SynapsePlugin>();
}
// get the jni stuff
new AndroidJavaClass("com.synapse.unityplugins.PermissionGranter");
initialized = true;
}
public void Awake() {
DontDestroyOnLoad (this.gameObject);
// instance is also set in initialize.
// having it here ensures this thing doesnt break
// if you added this component to the scene manually
instance = this;
if (name != PLUGIN_LISTENER_NAME)
name = PLUGIN_LISTENER_NAME;
}
// we're calling this method from the java side.
// the method name and gameobject must match Java's UnitySendMessage
private void permissionRequestCallbackInternal(string message) {
bool permissionGranted = (message == PERMISSION_GRANTED);
if (PermissionRequestCallback != null)
PermissionRequestCallback(permissionGranted);
}
}

I've used Jason knight's answer to create this plugin that does the job, the whole code is available on the github repo.
There is also unity package file for easy integration.

Related

Select default launcher for an app not appear in Android 7

I have an app that run as a launcher, in Android 4 in work great but in Android 7 and 8 the select launcher dialog not appear
//
// Decompiled by Procyon v0.5.36
//
package com.r7developers.unityplugin;
import android.os.Build;
import android.annotation.TargetApi;
import android.os.Process;
import android.app.AppOpsManager;
import android.content.Intent;
import com.rvalerio.fgchecker.AppChecker;
import android.content.Context;
public class Plugin
{
static Context mContext;
static String mBundleIdentifier;
static AppChecker mAppChecker;
public static void init(final Context context, final String bundleIdentifier) {
Plugin.mContext = context;
Plugin.mBundleIdentifier = bundleIdentifier;
}
public static void start() {
Plugin.mAppChecker = new AppChecker();
Plugin.mAppChecker.other((AppChecker.Listener)new AppChecker.Listener() {
public void onForeground(final String packageName) {
if (packageName != null && !packageName.contains(Plugin.mBundleIdentifier)) {
final Intent startHomescreen = new Intent("android.intent.action.MAIN");
startHomescreen.addCategory("android.intent.category.HOME");
startHomescreen.setFlags(268435456);
Plugin.mContext.startActivity(startHomescreen);
}
}
}).timeout(1000).start(Plugin.mContext);
}
public static void stop() {
Plugin.mAppChecker.stop();
}
public static void requestUsageStatsPermission() {
if (needsUsageStatsPermission() && !hasUsageStatsPermission()) {
Plugin.mContext.startActivity(new Intent("android.settings.USAGE_ACCESS_SETTINGS"));
}
}
#TargetApi(19)
public static boolean hasUsageStatsPermission() {
final AppOpsManager appOps = (AppOpsManager)Plugin.mContext.getSystemService("appops");
final int mode = appOps.checkOpNoThrow("android:get_usage_stats", Process.myUid(), Plugin.mContext.getPackageName());
final boolean granted = mode == 0;
return granted;
}
public static boolean needsUsageStatsPermission() {
return Build.VERSION.SDK_INT >= 21;
}
public static void openSettings() {
final Intent intent = new Intent("android.settings.SETTINGS");
intent.addFlags(268435456);
Plugin.mContext.startActivity(intent);
}
}
This code is decompiled from a jar plugin from unity project
Is there anything I'm missing?
Your problem must be caused by some missing line in the manifest.
Android evolve really quickly and some application originally made for older version might not work anymore due to changing in the permission system.
That must apply to the Android Manifest as well. By luck you got it working on an older device, but it seems that handling newer ones is problematic.
Have you tried to recompile a way smaller version of the code and test it on multiple version ?
Just by looking at some exemple launcher (but made without the Unity Engine), can help you understand if you have miss something.
Here is a link to an Android Launcher I found (the first one) on GitHub: KISS/app/src/main/AndroidManifest.xml.
Try adding the line that you are missing from your, especially the intent-filter that help Android know what your apps should be aware of when showing it in menus, like the Launcher selector.
I am not an expert, but I know that working with Android can be a pain.

How to mock permissions for testing in Android?

With Android 6.0 and new permission model, I am checking if the permission exists before performing certain task.
I want to assign these permissions to available and not available for testing purpose. I have a static class to check various permissions depending on the string.
boolean result = ContextCompat.checkSelfPermission(context, name) == PackageManager.PERMISSION_GRANTED;
Can it be achieved using Mockito or Roboelectric?
If you move your permission checker to a collaborator class, you can mock the collaborator. I am not familiar with the Android return types but the solution would look like this:
class PermissionsChecker {
public String checkSelfPermission(context, name) {
return ContextCompat.checkSelfPermission(context, name);
}
}
class YourApp {
private PermissionsChecker permissionsChecker; //need to inject this via setter or constructor
public doSomething() {
boolean result = permissionsChecker.checkSelfPermission(context, name).equals(PackageManager.PERMISSION_GRANTED);
}
}
class YourAppTest {
PermissionsChecker permissionsChecker = mock(PermissionsChecker.class);
#InjectMocks YourApp app = new YourApp();
#Test
public void hasPermissions() {
when(permissionsChecker.checkSelfPermission(...)).thenReturn("NOT GRANTED");
app.something();
//verify you get some error
}
}

How can I access neo4j running on ( any http or localhost) from android

I am successfully implemented neo4j on both mac and java application but I cannot access the same from and android and the it crashes at dbpath.But it keeps crashing.How can I get it to work?
INstead of
graphDb = new EmbeddedGraphDatabase(DB_PATH);
it is
RestAPI graphDb = new RestAPIFacade("http://localhost:7474/db/data");
also tried
GraphDatabaseService graphDb=new RestGraphDatabase(“http://localhost:7474/db/data”);
Entire Code:
import java.io.File;
import java.io.IOException;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.kernel.impl.util.FileUtils;
public class EmbeddedNeo4j {
private static final String DB_PATH = "/home/User/Documents/neo4j/";
String greeting;
// START SNIPPET: vars
GraphDatabaseService graphDb;
Node firstNode;
Node secondNode;
Relationship relationship;
// END SNIPPET: vars
// START SNIPPET: createReltype
private static enum RelTypes implements RelationshipType {
KNOWS
}
// END SNIPPET: createReltype
public static void main(final String[] args) {
EmbeddedNeo4j hello = new EmbeddedNeo4j();
hello.createDb();
hello.removeData();
hello.shutDown();
}
void createDb() {
clearDb();
// START SNIPPET: startDb
graphDb = new EmbeddedGraphDatabase(DB_PATH);
registerShutdownHook(graphDb);
// END SNIPPET: startDb
// START SNIPPET: transaction
Transaction tx = graphDb.beginTx();
try {
// Mutating operations go here
// END SNIPPET: transaction
// START SNIPPET: addData
firstNode = graphDb.createNode();
firstNode.setProperty("message", "Hello, ");
secondNode = graphDb.createNode();
secondNode.setProperty("message", "World!");
relationship = firstNode.createRelationshipTo(secondNode,
RelTypes.KNOWS);
relationship.setProperty("message", "brave Neo4j ");
// END SNIPPET: addData
// START SNIPPET: readData
System.out.print(firstNode.getProperty("message"));
System.out.print(relationship.getProperty("message"));
System.out.print(secondNode.getProperty("message"));
// END SNIPPET: readData
greeting = ((String) firstNode.getProperty("message"))
+ ((String) relationship.getProperty("message"))
+ ((String) secondNode.getProperty("message"));
// START SNIPPET: transaction
tx.success();
} finally {
tx.finish();
}
// END SNIPPET: transaction
}
private void clearDb() {
try {
FileUtils.deleteRecursively(new File(DB_PATH));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void removeData() {
Transaction tx = graphDb.beginTx();
try {
// START SNIPPET: removingData
// let's remove the data
firstNode.getSingleRelationship(RelTypes.KNOWS, Direction.OUTGOING)
.delete();
firstNode.delete();
secondNode.delete();
// END SNIPPET: removingData
tx.success();
} finally {
tx.finish();
}
}
void shutDown() {
System.out.println();
System.out.println("Shutting down database ...");
// START SNIPPET: shutdownServer
graphDb.shutdown();
// END SNIPPET: shutdownServer
}
// START SNIPPET: shutdownHook
private static void registerShutdownHook(final GraphDatabaseService graphDb) {
// Registers a shutdown hook for the Neo4j instance so that it
// shuts down nicely when the VM exits (even if you "Ctrl-C" the
// running example before it's completed)
Runtime.getRuntime().addShutdownHook(new Thread() {
#Override
public void run() {
graphDb.shutdown();
}
});
}
// END SNIPPET: shutdownHook
}
EmbeddedGraphDatabase can only be used if the DB and your client code should reside in the same JVM (therefore the word 'embedded').
If you want to remote access a Neo4j server the best ways today is either communicating with the transactional Cypher endpoint directly or using the Neo4j JDBC driver. Please note, in both cases you use Cypher to interact with the graph.
The library for java rest bindings is originated in the days where the two mentioned approached where not yet in place - so java-rest-bindings will be deprecated in the future.

Android memory leak in Apache Harmony's JarURLConnectionImpl?

I'm working on an Android app and we're investigating memory use.
Looking at a heap dump from hprof, we're seeing nearly 2M (22% of our heap) being used in a static cache in JarURLConnectionImpl:
Looking at the source code for JarURLConnectionImpl, it appears that entries are added to the static jarCache variable, but never removed.
If it's true that they're never removed, that strikes me as a potential memory leak.
Is this a leak? Is there a fix or workaround?
Here's an ugly workaround:
private static HashMap<URL,JarFile> jarCache;
static {
try {
Class<?> jarURLConnectionImplClass = Class.forName("org.apache.harmony.luni.internal.net.www.protocol.jar.JarURLConnectionImpl");
final Field jarCacheField = jarURLConnectionImplClass.getDeclaredField("jarCache");
jarCacheField.setAccessible(true);
//noinspection unchecked
jarCache = (HashMap<URL, JarFile>) jarCacheField.get(null);
} catch(Exception e) {
// ignored
}
}
Then, periodically run the following:
// HACK http://stackoverflow.com/questions/14610350/android-memory-leak-in-apache-harmonys-jarurlconnectionimpl
if( jarCache!=null ) {
try {
for (
final Iterator<Map.Entry<URL, JarFile>> iterator = jarCache.entrySet().iterator(); iterator.hasNext(); ) {
final Map.Entry<URL, JarFile> e = iterator.next();
final URL url = e.getKey();
if (Strings.toString(url).endsWith(".apk")) {
Log.i(TAG,"Removing static hashmap entry for " + url);
try {
final JarFile jarFile = e.getValue();
jarFile.close();
iterator.remove();
} catch( Exception f ) {
Log.e(TAG,"Error removing hashmap entry for "+ url,f);
}
}
}
} catch( Exception e ) {
// ignored
}
}
I run it on activity creation so it gets executed every time one of my activities is created. The ugly hashmap entry doesn't seem to get recreated all that often, but it DOES seem to reappear occasionally, so it's not sufficient to just run this code once.
This is definitely a nasty memory leak. I've opened an issue for it since no one else seems to have reported it.
Thanks for the "ugly workaround" emmby, that was helpful. A safer approach, although potentially with a performance impact, is to disable URLConnection caching altogether. Since the URLConnection.defaultUseCaches flag is static and, as you might guess, is the default for each instance's useCaches flag, you can just set this to false and no more instances will cache their connections. This will affect all implementations of URLConnection, so it may have farther-ranging effects than desired, but I think it's a reasonable trade-off.
You can just create a simple class like this and instantiate it very early in your app's onCreate():
public class URLConnectionNoCache extends URLConnection {
protected URLConnectionNoCache(URL url) {
super(url);
setDefaultUseCaches(false);
}
public void connect() throws IOException {
}
}
The interesting thing is that since this occurs after your app is loaded and run, the system libs should already be cached, and this will only prevent further caching, so this probably gives the best possible trade-off: not caching your apk while allowing the performance benefits of caching the system jars.
Before doing this I did modify emmby's solution a bit to make it a standalone class that creates a background thread to periodically clear the cache. And I restricted it to just clear the app's apk, though that can be relaxed if desired. The thing to worry about here is that you're modifying the objects while they may be in use, which is generally not a good thing. If you do want to go this route you just need to call the start() method with a context, e.g. in your app's onCreate().
package com.example;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import android.content.Context;
// hack to remove memory leak in JarURLConnectionImpl
// from http://stackoverflow.com/questions/14610350/android-memory-leak-in-apache-harmonys-jarurlconnectionimpl
public class JarURLMonitor {
private static JarURLMonitor instance;
private Pattern pat;
private Field jarCacheField;
public volatile boolean stop;
private static final long CHECK_INTERVAL = 60 * 1000;
public static synchronized void start(Context context) {
if (instance == null) {
instance = new JarURLMonitor(context);
}
}
public static synchronized void stop() {
if (instance != null) {
instance.stop = true;
}
}
private JarURLMonitor(Context context) {
// get jar cache field
try {
final Class<?> cls = Class.forName("libcore.net.url.JarURLConnectionImpl");
jarCacheField = cls.getDeclaredField("jarCache");
jarCacheField.setAccessible(true);
}
catch (Exception e) {
// log
}
if (jarCacheField != null) {
// create pattern that matches our package: e.g. /data/app/<pkgname>-1.apk
pat = Pattern.compile("^.*/" + context.getPackageName() + "-.*\\.apk$");
// start background thread to check it
new Thread("JarURLMonitor") {
#Override
public void run() {
try {
while (!stop) {
checkJarCache();
Thread.sleep(CHECK_INTERVAL);
}
}
catch (Exception e) {
// log
}
}
}.start();
}
}
private void checkJarCache() throws Exception {
#SuppressWarnings("unchecked")
final HashMap<URL, JarFile> jarCache = (HashMap<URL, JarFile>)jarCacheField.get(null);
final Iterator<Map.Entry<URL, JarFile>> iterator = jarCache.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<URL, JarFile> entry = iterator.next();
final JarFile jarFile = entry.getValue();
final String file = jarFile.getName();
if (pat.matcher(file).matches()) {
try {
jarFile.close();
iterator.remove();
}
catch (Exception e) {
// log
}
}
}
}
}

trouble setting up a db4o database

I am trying to use a singleton class to create this database because I need it to be open for each of the activities in the application. I keep getting a null pointer error from either the "getFilesDir" or "if(instance == null) calls, and since I am not that familiar with android programming I am not sure what is going on.
package com.database;
import java.io.File;
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
import com.businessclasses.Field;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.config.AndroidSupport;
import com.db4o.config.EmbeddedConfiguration;
public final class anotherTest extends Application {
private static ObjectContainer playsDB;
private static ObjectContainer gamePlanDB;
private anotherTest instance;
public anotherTest(){
final EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().add(new AndroidSupport());
//instance.getFilesDir().getPath()
new File(getFilesDir().getPath(), "/PlaysDB.db4o").delete();
Log.d("db", "" + instance.getFilesDir().getPath());
//getFilesDir().getPath();
//String appPath = "/data/data/PlaysDB/database";
playsDB = Db4oEmbedded.openFile(config, getFilesDir().getPath() + "/PlaysDB.db4o");
//File test = new File(appPath + "/PlaysDB.db4o");
//if(test != null){
// Log.d("db", "file was created");
// Log.d("db", "path >> " + test.getAbsolutePath());
//test.delete();
//}
//playsDB = Db4oEmbedded.openFile(config, appPath + "/PlaysDB.db4o");
}
public synchronized anotherTest getInstance(){
if(instance == null){
instance = new anotherTest();
}
return instance;
}
public void storePlay(Field field){
if(field != null){
if(playsDB == null){
Log.d("db", "database is null");
}
playsDB.store(field);
playsDB.commit();
Log.d("added play", "play added to db");
}
else{
Log.e("field input null", "play not added to db");
}
}
}
And here is my call in one of the activities.
public void onClick(View v) {
String formation = playFormation.getText().toString();
String name = playName.getText().toString();
String type = playType.getSelectedItem().toString();
//TODO:send to database
Field newField = new Field();
newField.setPlayName(name);
newField.setPlayType(type);
anotherTest temp = new anotherTest();
temp.getInstance().storePlay(newField);
}
Have been trying to make this database work for the past few weeks to no avail. Any help or guidance would be greatly appreciated.
The way this is structured doesn't make sense.
Java classes are always named with proper case - so it should be "AnotherTest".
You're not actually creating a singleton, because neither your instance, nor your getInstance() are static. Also, you never access your instance via "getInstance()".
You have a class AnotherTest, which has as a field an instance of AnotherTest, which is never, ever initialised, and which is accessed in the constructor of AnotherTest. This is just guaranteed to be null.
You are relying on implicit state of the Application superclass, which is never, ever provided because Application is supposed to be declared in the androidmanifest.xml, where it will be constructed for you. Meanwhile, you are constructing an instance manually in your event handler, so it has none of the state you expect - but anyway, it will fall over in its own constructor because it tries to reference a null pointer to itself, as mentioned above.
Your problems are nothing to do with DB40, and everything to do with not understanding what you're doing.
It needs to be way more like:
public final class AnotherTest {
private static AnotherTest instance;
private final Context context;
private AnotherTest(Context context){
this.context = context;
// Do whatever you need to do with DB4O using *only* the supplied context here
}
public static synchronized AnotherTest getInstance(Context context){
if(instance == null){
instance = new AnotherTest(context);
}
return instance;
}
}
And in the event handler:
AnotherTest.getInstance(Context.getApplicationContext()).storePlay(newField);

Categories

Resources