I have limit knowledge of Refbase, sp, wp of android. As far as I know if the refcount of one sp(strong point) decreased to 0, then the object wrapped in sp would be deleted.
Now I want to debug a snippet of codes from android AOSP, it looks like:
void OMXClient::disconnect() {
ALOGI("[Alan] OMXClient::disconnect!!!");
if (mOMX.get() != NULL) {
ALOGI("[Alan] will mOMX.clear() !!!");
mOMX.clear();
mOMX = NULL;
}
}
I want to print the refCount of mOMX every time before mOMX.clear().
Is it possible to do that ?
Related
I am using Unity along with the Google Play Services Plugin for Unity found over here: https://github.com/playgameservices/play-games-plugin-for-unity
I am trying to access the players avatar to be included in a profile pic ingame. The problem is when I try accessing the Texture2D Social.localuser.image, it always returns null. Upon more research it seems that the code uses some kind of AvatarURL to find the image and that is the thing that is null. I used string.IsNullOrEmpty(AvatarURL) to check this. Does anyone know why AvatarURL is null, and/or how I can fix it. If not that, is there any alternative way to accessing the players avatar to use for a profile picture in my game.
Here is the code I used to test this:
PlayGamesPlatform.Activate();
//Authenticate User
Social.localUser.Authenticate((bool success) => {
if(success) {
Debug.Log("Successfully Authenticated");
Textures.profilePic = Sprite.Create(Social.localUser.image, new Rect(0, 0, Social.localUser.image.width, Social.localUser.image.height), new Vector2(0.5f, 0.5f));
SceneManager.LoadScene("Main Menu");
} else {
Debug.Log("Failed to Authenticate User");
SceneManager.LoadScene("ErrorCanNotSignIn");
}
});
The error happens when setting Textures.profilePic (Textures is another class I created that stores textures, and profilePic is a static Sprite variable in it). It says there is a NullReferenceException: Object reference not set to an instance of the object.
Again based on what I've seen, I think the source of the error seems to be the AvatarURL being null, as it causes this code, which I am pretty sure is what loads the image, not to run:
if (!mImageLoading && mImage == null && !string.IsNullOrEmpty(AvatarURL))
{
Debug.Log("Starting to load image: " + AvatarURL);
mImageLoading = true;
PlayGamesHelperObject.RunCoroutine(LoadImage());
}
Also if it's important, I am testing this on an android device.
Question answered by JeanLuc on this SO question
The implementation for Social.localUser.image of the Play Games Unity
Plugin returns always null.
The reason why the image returns null is because it's loaded asynchronously, so you'll need to wait for it.
Furthermore there's a bug where you first need to access Social.localUser.userName.
This code should be working:
Debug.Log(Social.localUser.userName); // you need to access Social.localUser.userName, otherwise the image will always return null
StartCoroutine(KeepCheckingAvatar());
// ...
private IEnumerator KeepCheckingAvatar()
{
float secondsOfTrying = 20;
float secondsPerAttempt = 0.2f;
while (secondsOfTrying > 0)
{
if (Social.localUser.image != null)
{
// Do something with freshly loaded image
break;
}
secondsOfTrying -= secondsPerAttempt;
yield return new WaitForSeconds(secondsPerAttempt);
}
}
Credits:
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1056#issuecomment-212257972
In Android, I'd like to write SharedPreferences key-value pairs where the keys are Base64 strings.
// get a SharedPreferences instance
SharedPreferences prefs = getSharedPreferences("some-name", Context.MODE_PRIVATE);
// generate the base64 key
String someKey = new String(Base64.encode("some-key".getBytes("UTF-8"), Base64.URL_SAFE), "UTF-8");
// write the value for the generated key
prefs.edit().putBoolean(someKey, true).commit();
In the last line, the call to commit returns true. So this key-value pair should have been saved successfully.
When I close and destroy the Activity where this piece of code was used and then re-create the Activity (running this code again), the specified value is returned for the key that we used.
But it turns out that, when I destroy the whole application/process (e.g. using "Force stop" in app settings), the value for our key is lost on the next launch of the Activity.
When I don't use Base64.URL_SAFE but Base64.URL_SAFE | Base64.NO_WRAP as the flags for the Base64 encoding, it works fine.
So this problem has been caused by the newlines at the end of the Base64 keys. Keys like abc can be written without any problems. But when the key is abc\n, it fails.
The problem is that it appears to work without problems first, returning true on commit() and returning the correct preference value on subsequent calls. But when the whole application is destroyed and re-started, the value has not been persisted.
Is this intended behaviour? A bug? Does the documentation say anything about valid key names?
I took a look at GrepCode and see that the operations will be the following (I do not mention useless ones) :
android.app.SharedPreferencesImpl.commit()
android.app.SharedPreferencesImpl.commitToMemory()
android.app.SharedPreferencesImpl.queueDiskWrite(MemoryCommitResult,Runnable)
3.1. XmlUtils.writeMapXml(Map, OutputStream)
3.2. XmlUtils.writeMapXml(Map, String, XmlSerializer)
3.3. XmlUtils.writeValueXml(Object v, String name, XmlSerializer ser)
First : how your data are converted ?
The method XmlUtils.writeValueXml writes the Object value in a XML tag with the attribute name set to the String value. This String value contains exactly the value you specified at the SharedPreference's name.
(And I confirmed this by doing a step-by-step debug with your piece of code).
The XML will be with an unescaped line break character. Actually, the XmlSerializer instance is a FastXmlSerializer instance and it does not escape the \n character (see the link for this class at the end if you want to read the source code)
Interesting piece of code :
writeValueXml(Object v, String name, XmlSerializer out) {
// -- "useless" code skipped
out.startTag(null, typeStr);
if (name != null) {
out.attribute(null, "name", name);
}
out.attribute(null, "value", v.toString());
out.endTag(null, typeStr);
// -- "useless" code skipped
}
Second : why the result is true ?
The commit method has the following code :
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
So it returns the mcr.writeToDiskResult which is set in the SharedPreferencesImpl.writeToFile(MemoryCommitResult) method. Interesting piece of code :
writeToFile(MemoryCommitResult mcr) {
// -- "useless" code skipped
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Libcore.os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// -- "useless" code skipped
}
As we see at the previous point : the XML writing is "ok" (do not throw anything, do not fails), so the sync in the file will be too (just a copy of a Stream in another one, nothing checks the XML content here !).
Currently : your key was converted to (badly formatted) XML and correctly wrote in a File. The result of the whole operation is true as everything went OK. Your changes are comitted to the disk and in the memory.
Third and last : why do I get back the correct value the first time and a bad one the second time
Take a quick look to what happen when we commit the changes to memory in SharedPreferences.Editor.commitToMemory(...) method (interesting part only... :)):
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this) { // magic value for a removal mutation
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
boolean isSame = false;
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
Important point : the changes are commited to the mMap attribute.
Then, take a quick look to how we get back a value :
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
We are taking back the key from mMap (no reading of the value in the file for now). So we have the correct value for this time :)
When you reload your application, you will load the data back from the disk, and so the SharedPreferencesImpl constructor will be called, and it will call the SharedPreferencesImpl.loadFromDiskLocked() method. This method will read the file content and load it in the mMap attribute (I let you see the code by yourself, link provided at the end).
A step-by-step debug shown me that the abc\n was written as abc (with a whitespace character). So, when you will try to get it back, you will never succeed.
To finish, thank you to #CommonsWare to give me a hint about the file content in the comment :)
Links
XmlUtils
FastXmlSerializer
SharedPreferencesImpl
SharedPreferencesImpl.EditorImpl.commit()
SharedPreferencesImpl.EditorImpl.commitToMemory()
SharedPreferencesImpl.enqueueDiskWrite(MemoryCommitResult, Runnable)
SharedPreferencesImpl.writeToFile(MemoryCommitResult)
SharedPreferencesImpl.loadFromDiskLocked()
I am trying to test a sample project called Android.Routing.Offline from OsmSharp.Samples in Github.
After two taps on the screen (the first one gets just the GeoCoordinate) I get a ProtoBuf.ProtoException in the Router.cs
private static IBasicRouterDataSource<CHEdgeData> _graph;
public static void Initialize()
{
var routingSerializer = new CHEdgeDataDataSourceSerializer();
_graph = routingSerializer.Deserialize(
Assembly.GetExecutingAssembly().GetManifestResourceStream(#"Android.Routing.Offline.kempen-big.contracted.mobile.routing"));
}
public static Route Calculate(GeoCoordinate from, GeoCoordinate to)
{
try
{
lock(_graph)
{
var router = Router.CreateCHFrom(_graph, new CHRouter(), new OsmRoutingInterpreter());
// The exception happens here below
var fromResolved = router.Resolve(Vehicle.Car, from);
var toResolved = router.Resolve(Vehicle.Car, to);
if(fromResolved != null && toResolved !=null)
{
return router.Calculate(Vehicle.Car, fromResolved, toResolved);
}
}
}
catch(Exception ex)
{
OsmSharp.Logging.Log.TraceEvent("Router", OsmSharp.Logging.TraceEventType.Critical, "Unhandled exception occured: {0}", ex.ToString());
}
return null;
}
And the exception:
> {ProtoBuf.ProtoException: Invalid wire-type; this usually means you
> have over-written a file without truncating or setting the length; see
> http://stackoverflow.com/q/2152978/23354 at
> ProtoBuf.ProtoReader.ReadSingle () ...
I didnt overwrite the file (kempen-big.contracted.mobile.routing) just added it as a linked file in the project. Any ideas how I can solve this issue?
Well, the first thing to try is to check that the contents of the Stream you are reading (via GetManifestResourceStream) contains exactly the contents you are expecting, and not some wrapper or otherwise-corrupt mess. If you have some checksum algorithm you can run: great! Checking just the .Length would be a great start. Otherwise, you could cheat (just for the purposes of validating the contents) by getting the hex:
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
string hex = BitConverter.ToString(
ms.GetBuffer(), 0, (int)ms.Length);
// dump this string, and compare it to the same output run on the
// oringal file; they should be identical
}
Note that this duplicates the contents in-memory, purely so we can get a byte[] (oversized) to get the hex from - it isn't intended for "real" code, but until you are sure that the contents are correct, all other bets are off. I strongly suspect that you'll find that the contents are not identical to the contents in the original file. Note that I'm also implicitly assuming that the original file works fine in terms of deserialization. If the original file doesn't work: again, all bets are off.
Background
I've noticed a weird column for MediaStore.Images.ImageColumns called "MINI_THUMB_MAGIC" .
the documentation says just that :
The mini thumb id.
Type: INTEGER
Constant Value: "mini_thumb_magic"
The question
my guess is that this field is related to MediaStore.Images.Thumbnails .
Is it correct ? if not, what is this and how do you use it?
if it is correct , i have other questions related to it:
Is it a mini sized image of the original one? does it use the same aspect ratio or does it do center-cropping on it?
how come the size of "MICRO" is square (96 x 96) and the size of "MINI" is a non-square rectangle ( 512 x 384 ) ?
How do you use it? My guess is that it's done by using "THUMB_DATA", which is a blob, so you use it like this, but then what is the purpose of using "getThumbnail" if you already have this field?
does it get a rotated thumbnail in case the orientation value is not 0 ? meaning that if I wish to show it, I won't need to rotate the image?
Is it possible to do a query of the images together with their thumbnails? maybe using inner join?
Is it available for all Android devices and versions?
Why is it even called "magic" ? Is it because it's also available for videos (and for some reason doesn't exist for music, as it could be the album's cover photo, for example) ?
Check this file: https://github.com/android/platform_packages_providers_mediaprovider/blob/master/src/com/android/providers/media/MediaThumbRequest.java in the Android source code. This value is some magic number which allows to determine if the thumbnail is still valid. I didn't investigate that file further, but it should be no bit issue to dive deeper.
To your questions:
No, no mini-sized image
Well, I guess it's a definition by Google who want to have a square thumbnail for some lists, where only very small previews should be visible and where many items should fit on the screen and there's another thumbnail format where the images are bigger...
I don't know that, but according to Google's doc, one (THUMB_DATA) is only some raw byte array of the thumbnail (dunno in which format) and the other one (getThumbnail) retrieves a full-fledged bitmap object...
don't know
don't know
I guess so, as it's part of AOSP source code.
The word "magic" is often used for some kind of identifier. There are "magic packets" who can wake up a computer from sleep or shutdown over the network, there are magic numbers on hard disks, where some sectors (e.g. the MBR) has the hexadecimal values AA 55 on its last two byte positions, there are also magic numbers in image files which help software packages determine the image type (e.g. GIF files begin with GIF89a or GIF87a (ASCII), JPEG files begin with FF D8 hexadecimal) and there are many, many more examples. So, magic numbers are a very common term here :-)
According to the source code at the following URL, the Magic Number is the Id of the original image * a constant. That value is then used to check for a long int. If the int isn't as expected, it's considered out of sync with the image media.
http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/4.4_r1-robolectric-0/android/media/MiniThumbFile.java#MiniThumbFile.getMagic%28long%29
// Get the magic number for the specified id in the mini-thumb file.
// Returns 0 if the magic is not available.
public synchronized long getMagic(long id) {
// check the mini thumb file for the right data. Right is
// defined as having the right magic number at the offset
// reserved for this "id".
RandomAccessFile r = miniThumbDataFile();
if (r != null) {
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
mBuffer.clear();
mBuffer.limit(1 + 8);
lock = mChannel.lock(pos, 1 + 8, true);
// check that we can read the following 9 bytes
// (1 for the "status" and 8 for the long)
if (mChannel.read(mBuffer, pos) == 9) {
mBuffer.position(0);
if (mBuffer.get() == 1) {
return mBuffer.getLong();
}
}
} catch (IOException ex) {
Log.v(TAG, "Got exception checking file magic: ", ex);
} catch (RuntimeException ex) {
// Other NIO related exception like disk full, read only channel..etc
Log.e(TAG, "Got exception when reading magic, id = " + id +
", disk full or mount read-only? " + ex.getClass());
} finally {
try {
if (lock != null) lock.release();
}
catch (IOException ex) {
// ignore it.
}
}
}
return 0;
}
I got the runtime exception when trying to get the original Id of a thumbnail by looking up the thumbnail's path. (BTW, the disk isn't full and it's not read-only.)
It's a bit strange parameter...
While exploring the Gallery source code,
noticed that the value is being read from the cursor, but then is Never used:
#Override
protected BaseImage loadImageFromCursor(Cursor cursor) {
long id = cursor.getLong(INDEX_ID);
String dataPath = cursor.getString(INDEX_DATA_PATH);
long dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
if (dateTaken == 0) {
dateTaken = cursor.getLong(INDEX_DATE_MODIFIED) * 1000;
}
// here they read it ====>>
long miniThumbMagic = cursor.getLong(INDEX_MINI_THUMB_MAGIC);
int orientation = cursor.getInt(INDEX_ORIENTATION);
String title = cursor.getString(INDEX_TITLE);
String mimeType = cursor.getString(INDEX_MIME_TYPE);
if (title == null || title.length() == 0) {
title = dataPath;
}
// and not use at all ==>>>
return new Image(this, mContentResolver, id, cursor.getPosition(),
contentUri(id), dataPath, mimeType, dateTaken, title,
orientation);
}
Maybe it was used on the previous APIs.
ref: https://android.googlesource.com/platform/packages/apps/Gallery/+/android-8.0.0_r12/src/com/android/camera/gallery/ImageList.java?autodive=0%2F%2F.
and videos list:
https://android.googlesource.com/platform/packages/apps/Gallery/+/android-8.0.0_r12/src/com/android/camera/gallery/VideoList.java?autodive=0%2F%2F
I've never really done much C and am a bit stumped on the best way to send a boolean from an Android app to the Pebble Watch.
I have strings working fine, but there doesn't seem to be an addBoolean method on PebbleDictionary. As a work around I am trying to use addUint8 to send a 1 or 0, but am having trouble handling the message on the Pebble.
Here is my Android code:
PebbleDictionary data = new PebbleDictionary();
if (isGPSFix()){
data.addUint8(GPS_HAS_FIX_KEY, Byte.valueOf("1"));
} else {
data.addUint8(GPS_HAS_FIX_KEY, Byte.valueOf("0"));
}
PebbleKit.sendDataToPebble(app.getContext(), UUID, data);
And in my Pebble I have a data struct:
static struct MyData {
uint8_t haveGPS[1];
.... // other stuff ommitted
AppSync sync;
uint8_t sync_buffer[256];
} s_data;
And then I am trying to compare it like this in my sync_tuple_changed callback.
static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) {
(void) old_tuple;
switch (key) {
case GPS_HAS_FIX_KEY:
if (memcmp(s_data.haveGPS, new_tuple->value->data, 8) == 0){
memcpy(s_data.haveGPS,new_tuple->value->data, new_tuple->length);
vibes_short_pulse();
}
break;
default:
return;
}
}
The watch doesn't crash, it just never vibrates when the phone drops or acquires GPS.
Things look good on the Android side. I think this is more of an AppSync problem.
Here are a few things to check in the watch application:
Make sure you create a list of tuples with initial values on the watch. This list needs to to contain your key GPS_HAS_FIX_KEY;
Tuplet initial_values[] = {
TupletInteger(GPS_HAS_FIX_KEY, (uint8_t) 0),
/* Other tuplets that you will synchronize */
};
Make sure you pass those tuplets to the app_sync_init() function:
app_sync_init(&sync, sync_buffer, sizeof(sync_buffer),
initial_values, ARRAY_LENGTH(initial_values),
sync_tuple_changed_callback, sync_error_callback, NULL);
Those two steps are required for app_sync to work (cf AppSync reference documentation).
As far as I understand you have to send an Dictionary of keys and objects to the watch. In Objective C it looks like this:
NSDictionary *update = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
Keys are all integer and objects I would think can be of Boolean. In my case only String what works well.
In a loop I perform something like this on the watch:
t=dict_find(received,count);
strcpy(strs[count], dict_find(received,count)->value->cstring);
But I have to tell you that I am still a rookie.
Thanks to sarfata's accepted answer above, I found that I had not added this new item to the Tuple that the Pebble was expecting.
Once I added that, my switch statement started working and I just had to get the memory compare to work. Here is the working code for my memory compare in case it helps anyone.
case GPS_HAS_FIX_KEY:
if (memcmp(s_data.haveGPS, new_tuple->value->data, 1) != 0){
memcpy(s_data.haveGPS,new_tuple->value->data, 1);
vibes_short_pulse();
}
break;
It really was a simple as expecting one byte (not 8 bytes - I thought it was doing a bit compare) and negating the logic for the case where the new value is NOT like the old one.