i use different savegames in my app. "coins" , "levels" , ...
It works fine but if a conflict detected then its wrong result.
/**
* Conflict resolution for when Snapshots are opened. Must be run in an AsyncTask or in a
* background thread,
*/
Snapshots.OpenSnapshotResult processSnapshotOpenResult(Snapshots.OpenSnapshotResult result, int retryCount) {
retryCount++;
int status = result.getStatus().getStatusCode();
Log.i(TAG, "Load Result for saveGame<" + savedGame.getName() + "> status: " + status);
if (status == GamesStatusCodes.STATUS_OK) {
return result;
} else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE) {
return result;
} else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) {
saveResolveConflictGameData = true;
Log.i(TAG, "Konflikt aufgetreten");
Snapshots.OpenSnapshotResult resolveResult = null;
Snapshot snapshot = result.getSnapshot();
Snapshot conflictSnapshot = result.getConflictingSnapshot();
Snapshot mResolvedSnapshot = null;
mResolvedSnapshot = snapshot;
SnapshotMetadata s1Meta = snapshot.getMetadata();
SnapshotMetadata cMeta = conflictSnapshot.getMetadata();
// resolveConflict and get new merged Parser Object
//
Parser conflictParserTemp = savedGame.resolveConflict(snapshot, conflictSnapshot);
if ( conflictParserTemp == null) {
Log.e(TAG, "savedGame.resolveConflict(snapshot,conflictSnapshot) Error");
return result;
}
//
// wurde schon ein conflict behandelt ?
//
if ( conflictParser != null ) {
// merge previous Conflict with this conflict
conflictParser.merge(conflictParserTemp);
} else {
// set first conflict Parser
conflictParser = conflictParserTemp;
}
Log.i(TAG, String.format("Games.Snapshots.resolveConflict() Step %d", retryCount));
resolveResult =
Games.Snapshots.resolveConflict(
activity.mGoogleApiClient, result.getConflictId(), mResolvedSnapshot).await();
if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
// Recursively attempt again
return processSnapshotOpenResult(resolveResult, retryCount);
} else {
// Failed, log error and show Toast to the user
String message = "Could not resolve snapshot conflicts";
Log.e(TAG, message);
Toast.makeText(activity.getBaseContext(), message, Toast.LENGTH_LONG).show();
return resolveResult;
}
}
// Fail, return null.
return null;
}
The Error is that if I load savegame "coins" I become all conflicts from other savegames.
I see it here.
SnapshotMetadata s1Meta = snapshot.getMetadata();
SnapshotMetadata cMeta = conflictSnapshot.getMetadata();
The Snapshot for korrekt coins savegame show this:
SnapshotMetadataEntity{Game=GameEntity{ApplicationId=520840013521,
DisplayName=Crush me, PrimaryCategory=Simulation,
SecondaryCategory=null, Description=hallo, DeveloperName=steffen
höhmann, IconImageUri=null, IconImageUrl=null, HiResImageUri=null,
HiResImageUrl=null, FeaturedImageUri=null, FeaturedImageUrl=null,
PlayEnabledGame=true, InstanceInstalled=true,
InstancePackageName=cherry.de.wubbleburst, AchievementTotalCount=0,
LeaderboardCount=0, RealTimeMultiplayerEnabled=false,
TurnBasedMultiplayerEnabled=false, AreSnapshotsEnabled=true,
ThemeColor=00456B, HasGamepadSupport=false},
Owner=PlayerEntity{PlayerId=113260033482974102226,
DisplayName=shoehmi, HasDebugAccess=false, IconImageUri=null,
IconImageUrl=null, HiResImageUri=null, HiResImageUrl=null,
RetrievedTimestamp=1454003980807, Title=Anfänger,
LevelInfo=com.google.android.gms.games.PlayerLevelInfo#1e1b36},
SnapshotId=drive://113260033482974102226/520840013521/coins,
CoverImageUri=null, CoverImageUrl=null, CoverImageAspectRatio=0.0,
Description=null, LastModifiedTimestamp=1454004003382, PlayedTime=-1,
UniqueName=coins, ChangePending=true, ProgressValue=-1}
drive://113260033482974102226/520840013521/coins
and the snapshotData:
timestamp;coins#1453929273252;100#1453929280956;-70#230179;70
but he shows me savegame snaphot from "level" savegame as conflicted Snapshot:
levelId;points#1;3241#2;9634
and the Conflict Snapshot Metadata say it is a "coins" savegame:
SnapshotMetadataEntity{Game=GameEntity{ApplicationId=520840013521,
DisplayName=Crush me, PrimaryCategory=Simulation,
SecondaryCategory=null, Description=hallo, DeveloperName=steffen
höhmann, IconImageUri=null, IconImageUrl=null, HiResImageUri=null,
HiResImageUrl=null, FeaturedImageUri=null, FeaturedImageUrl=null,
PlayEnabledGame=true, InstanceInstalled=true,
InstancePackageName=cherry.de.wubbleburst, AchievementTotalCount=0,
LeaderboardCount=0, RealTimeMultiplayerEnabled=false,
TurnBasedMultiplayerEnabled=false, AreSnapshotsEnabled=true,
ThemeColor=00456B, HasGamepadSupport=false},
Owner=PlayerEntity{PlayerId=113260033482974102226,
DisplayName=shoehmi, HasDebugAccess=false, IconImageUri=null,
IconImageUrl=null, HiResImageUri=null, HiResImageUrl=null,
RetrievedTimestamp=1454003980807, Title=Anfänger,
LevelInfo=com.google.android.gms.games.PlayerLevelInfo#1e1b36},
SnapshotId=drive://113260033482974102226/520840013521/coins,
CoverImageUri=null, CoverImageUrl=null, CoverImageAspectRatio=0.0,
Description=null, LastModifiedTimestamp=1454004003382, PlayedTime=-1,
UniqueName=coins, ChangePending=true, ProgressValue=-1}
drive://113260033482974102226/520840013521/coins
Why only if conflict occured and without conflicts its running correct and
save / load correct??
Please Help me???
sorry for my english ;)
Related
I tried to create managed user using UserManager instead of DevicePolicyManager.But the log is showing
Cannot add more managed profiles for user android 0
The below code (AOSP code) is returning always false (UserManagerService.java)
(https://android.googlesource.com/platform/frameworks/base/+/a029ea1/services/java/com/android/server/pm/UserManagerService.java)
"hasSystemFeature false"
#Override
public boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne) {
checkManageUsersPermission("check if more managed profiles can be added.");
Log.e(LOG_TAG, "isLowRamDeviceStatic check");
if (ActivityManager.isLowRamDeviceStatic()) {
return false;
}
Log.e(LOG_TAG, "isLowRamDeviceStatic false");
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_MANAGED_USERS)) {
Log.e(LOG_TAG, "hasSystemFeature false");
return false;
}
Log.e(LOG_TAG, "hasSystemFeature true");
// Limit number of managed profiles that can be created
final int managedProfilesCount = getProfiles(userId, false).size() - 1;
final int profilesRemovedCount = managedProfilesCount > 0 && allowedToRemoveOne ? 1 : 0;
if (managedProfilesCount - profilesRemovedCount >= getMaxManagedProfiles()) {
return false;
}
Log.e(LOG_TAG, "managedProfilesCount "+ managedProfilesCount);
synchronized(mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
if (userInfo == null || !userInfo.canHaveProfile()) {
return false;
}
Log.e(LOG_TAG, "getUserInfoLU not null or userInfo.canHaveProfile()");
int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- profilesRemovedCount;
// We allow creating a managed profile in the special case where there is only one user.
return usersCountAfterRemoving == 1
|| usersCountAfterRemoving < UserManager.getMaxSupportedUsers();
}
}
The feature is enabled using below configuration. But not working
https://android.googlesource.com/platform/frameworks/native/+/master/data/etc/android.software.managed_users.xml
<permissions>
<feature name="android.software.managed_users" />
</permissions>
I'm using ZXing in an Android app being developed in Xamarin to scan a QR code and start playing the corresponding audio file automatically.
My problem is that when I get a result from scanning, it takes some time for the audio player activity to load so it gets called twice or more due to subsequent successful scannings.
Is there a way to stop continuous scanning as soon as I get a correct result?
Here's the code:
//Start scanning
scanner.ScanContinuously(opt, HandleScanResult);
}
private void HandleScanResult(ZXing.Result result)
{
string msg = "";
if (result != null && !string.IsNullOrEmpty(result.Text))
{
msg = result.Text;
var playerActivity = new Intent(myContext, typeof(AudioActivity));
//-------------------------------------------------------------
// Prerequisite: load all tracks onto "Assets/tracks" folder
// You can put here qr code - track assignments here below
// msg: decoded qr code
// playerActivity.Putextra second parameter is a relative path
// under "Assets" directory
//--------------------------------------------------------------
//Iterate through tracks stored in assets and load their titles into an array
System.String[] trackArray = Application.Context.Assets.List("tracks");
bool trackFound = false;
foreach (string track in trackArray)
{
if (track.Equals(msg + ".mp3"))
{
playerActivity.PutExtra("Track", "tracks/" + msg + ".mp3");
for (int i = 0; i < PostList.postList.Count; i++)
{
if (PostList.postList.ElementAt(i).code.Equals(msg))
playerActivity.PutExtra("TrackTitle", PostList.postList.ElementAt(i).title);
}
myContext.StartActivity(playerActivity);
trackFound = true;
}
}
Thank you!
Old question but i'll post it anyway for anyone still looking for this information.
You need your scanner to be a class variable. This is my code:
public MobileBarcodeScanner scanner = new MobileBarcodeScanner();
private void ArrivalsClick(object sender, EventArgs e)
{
try
{
if (Arrivals.IsEnabled)
{
MobileBarcodeScanningOptions optionsCustom = new MobileBarcodeScanningOptions();
scanner.TopText = "Scan Barcode";
optionsCustom.DelayBetweenContinuousScans = 3000;
scanner.ScanContinuously(optionsCustom, ArrivalResult);
}
}
catch (Exception)
{
throw;
}
}
private async void ArrivalResult(ZXing.Result result)
{
if (result != null && result.Text != "")
{
// Making a call to a REST API
if (resp.StatusCode == System.Net.HttpStatusCode.OK)
{
int? res = JsonConvert.DeserializeObject<int>(resp.Content);
if (res == 0)
{
scanner.Cancel(); // <----- Stops scanner (Something went wrong)
Device.BeginInvokeOnMainThread(async () =>
{
await DisplayAlert("..", "..", "ΟΚ");
});
}
else
{
Plugin.SimpleAudioPlayer.ISimpleAudioPlayer player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
player.Load("beep.wav");
player.Play(); // Scan successful
}
}
else
{
scanner.Cancel();
Device.BeginInvokeOnMainThread(async () =>
{
await DisplayAlert("..", "..", "ΟΚ");
});
}
}
}
In my game I have in-game currency and I want to save its value to the cloud. I decided to use Google Saved Games API. Everything works great but when I'm saving data to the Snapshots and then reading it when the game launches again, I'm getting conflicts, even when I'm on the same device. Now I'm saving currency's state after every change, so when player spents or gets some "coins". I'm thinking that this could be very often and services can't handle it because when I'm offline (without connection to the network) everything works nice and fast but when I'm online (connected to Wi-fi) work with Snapshots is slower and as I said I'm getting conflicts with data last saved and previous data I saved (I'm loggging all values...). Sometimes I get even 5 conflicts. I have 3 functions to work with Saved Games. One for reading data, one for saving data and one for checking for conflicts:
Reading data:
private void readSavedGame(final String snapshotName) {
AsyncTask<Void, Void, Boolean> readingTask = new AsyncTask<Void, Void, Boolean>() {
#Override
protected Boolean doInBackground(Void... params) {
Snapshots.OpenSnapshotResult result = Games.Snapshots.open(mGoogleApiClient, snapshotName, false).await();
Snapshot snapshot = processSnapshotOpenResult(result, 0);
if(snapshot != null) {
try {
updateGameData(snapshot.getSnapshotContents().readFully());
Log.d(TAG, "Updating game: "+String.valueOf(coins)+"...");
return true;
} catch (IOException e) {
Log.d(TAG, "Error: " + e.getMessage());
}
}
return false;
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if(result) Log.d(TAG, "Game state read successfully...");
else Log.d(TAG, "Error while reading game state...");
updateUi();
}
};
readingTask.execute();
}
Saving data:
private void writeSavedGame(final String snapshotName, final byte[] data) {
AsyncTask<Void, Void, Boolean> updateTask = new AsyncTask<Void, Void, Boolean>() {
#Override
protected Boolean doInBackground(Void... params) {
Snapshots.OpenSnapshotResult result = Games.Snapshots.open(
mGoogleApiClient, snapshotName, false).await();
Snapshot snapshot = processSnapshotOpenResult(result, 0);
if(snapshot != null) {
snapshot.getSnapshotContents().writeBytes(getGameData());
Log.d(TAG, "Saving: "+String.valueOf(coins)+"...");
Snapshots.CommitSnapshotResult commitSnapshotResult = Games.Snapshots.commitAndClose(mGoogleApiClient, snapshot, SnapshotMetadataChange.EMPTY_CHANGE).await();
if(commitSnapshotResult.getStatus().isSuccess()) return true;
}
return false;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) Log.d(TAG, "Game was saved successfully....");
else Log.d(TAG, "Error while saving game state...");
}
};
updateTask.execute();
}
Checking for conflicts or handling OpenSnapshotResult
Snapshot processSnapshotOpenResult(Snapshots.OpenSnapshotResult result, int retryCount) {
Snapshot mResolvedSnapshot = null;
retryCount++;
int status = result.getStatus().getStatusCode();
Log.i(TAG, "Save Result status: " + status);
if (status == GamesStatusCodes.STATUS_OK) {
Log.d(TAG, "No conflict, SNAPSHOT is OK");
return result.getSnapshot();
} else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE) {
return result.getSnapshot();
}
else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) {
Log.d(TAG, "Conflict: "+String.valueOf(retryCount));
Snapshot snapshot = result.getSnapshot();
Snapshot conflictSnapshot = result.getConflictingSnapshot();
// Resolve between conflicts by selecting the newest of the conflicting snapshots.
mResolvedSnapshot = snapshot;
if (snapshot.getMetadata().getLastModifiedTimestamp() <
conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
mResolvedSnapshot = conflictSnapshot;
}
try {
Log.d(TAG, "Snapshot data: "+new String(snapshot.getSnapshotContents().readFully()));
Log.d(TAG, "Conflicting data: "+new String(conflictSnapshot.getSnapshotContents().readFully()));
} catch (IOException e) {
Log.e(TAG, "ERROR WHILE READING SPAPSHOTS CONTENTS...");
}
Snapshots.OpenSnapshotResult resolveResult = Games.Snapshots.resolveConflict(
mGoogleApiClient, result.getConflictId(), mResolvedSnapshot).await();
if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
// Recursively attempt again
return processSnapshotOpenResult(resolveResult, retryCount);
} else {
// Failed, log error and show Toast to the user
String message = "Could not resolve snapshot conflicts";
Log.e(TAG, message);
//Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
}
}
// Fail, return null.
return null;
}
Conflict principes are explained nicely here.
My code is based on official docs implementations and samples.
So when offline, everything works excellent but when connected, I'm getting conflicts on the same device... Maybe I'm updating my saved game very often and services can't handle it. Any ideas? Thanks.
As discussed in Saved Games - Conflict resolution
Typically, data conflicts occur when an instance of your application is unable to reach the Saved Games service while attempting to load data or save it. In general, the best way to avoid data conflicts is to always load the latest data from the service when your application starts up or resumes, and save data to the service with reasonable frequency.
In addition to that, it is also recommended to follow Best practices for implementing saved games to deliver the best possible product to your players.
Also, to learn how to implement Saved Games for your platform, see the resources given in Client implementations.
I'm trying to get 'Change Subscriptions' to work using the Drive API for Android, but been unsuccessful so far.
Here the simple use case:
2 android devices, both using the same google account
both subscribe to the same 'file of interest' in their drive folder
if the file 'changes', be it from a change performed by one of the two devices or any external source, all devices that subscribed to this file are notified
As far as I understand, this is exactly what 'Change Subscriptions' are supposed to do for me. I'm using play services revision 27.
The problem I have:
A 'file content change' (or some other file event) made locally on one device is never properly propagated to the all other devices that subscribed to the same file.
Does anyone know of any solutions to this issue, or can point my to what I'm doing wrong?
I've written some simple testcode (see below), that only needs a connected googleApiClient, here's what I tested:
1.
device 1 creates a new testfile calling testFileWriteNew() and adds a change subscription to this file using testFileAddAndRemoveSubscription(), the expected log output:
testfile.txt created, driveId=DriveId:CAESABi0AyDAu9XZhVMoAA== resourceId=null
onCompletion; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
STATUS_SUCCESS
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
2.
device 2 adds a change subscription to the same file using testFileAddAndRemoveSubscription(), the expected log output:
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
As expected, the driveId is different on both devices, but the resourceId is the same 0B-sshen4iTFAN0htekFYNExuSEU, so that same 'cloud' file is referenced
3.
If I update the file with some new data via testFileUpdate I get the following on device 1:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
and device 2:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
4.
Unfortunately, the 'change of content' in the onChange method of the service is only triggered locally. A changed done by device 1 never reaches device 2 and vice versa. If I update the file using device 2 I see the following log on device 2 coming from the service:
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
contentChanged
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
metadataChanged
but I never see the onChange method being triggered on device 1, if device 2 triggered a change, which I would expect.
Code:
private boolean testFileWriteNew() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
DriveContentsResult contentsResult = Drive.DriveApi.newDriveContents(mGoogleApiClient).await();
if (!contentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = contentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
MetadataChangeSet originalMetadata = new MetadataChangeSet.Builder().setTitle("testfile.txt").setMimeType("text/plain").build();
// create the file in root
DriveFolder.DriveFileResult fileResult = folderRoot.createFile(mGoogleApiClient, originalMetadata, originalContents, new ExecutionOptions.Builder().setNotifyOnCompletion(true).build()).await();
if (!fileResult.getStatus().isSuccess()) {
return false;
}
// check 'locally created' file, not yet synced to drive
DriveResource.MetadataResult metadataResult = fileResult.getDriveFile().getMetadata(mGoogleApiClient).await();
if (!metadataResult.getStatus().isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt created, driveId=" + metadataResult.getMetadata().getDriveId().encodeToString() + " resourceId=" + metadataResult.getMetadata().getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileUpdate() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// update testfile
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
DriveContentsResult driveContentsResult = file.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, null).await();
if (!driveContentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = driveContentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
// commit changes
com.google.android.gms.common.api.Status status = originalContents.commit(mGoogleApiClient, null).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt updated, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileAddAndRemoveSubscription(boolean subscribe) {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// subscribe & unsubscribe
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
if(subscribe) {
com.google.android.gms.common.api.Status status = file.addChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "added subscription to testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} else {
com.google.android.gms.common.api.Status status = file.removeChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "removed subscription from testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
}
}
And here the service class:
public class ChangeService extends DriveEventService {
// TAG
private static final String TAG = ChangeService.class.getSimpleName();
#Override
public void onChange(ChangeEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onChange; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
if(event.hasContentChanged()) { Log.e(TAG, "contentChanged"); }
else if(event.hasMetadataChanged()) { Log.e(TAG, "metadataChanged"); }
else if(event.hasBeenDeleted()) { Log.e(TAG, "beenDeleted"); }
}
#Override
public void onCompletion(CompletionEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onCompletion; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
switch (event.getStatus()) {
case CompletionEvent.STATUS_CONFLICT: Log.e(TAG, "STATUS_CONFLICT"); break;
case CompletionEvent.STATUS_FAILURE: Log.e(TAG, "STATUS_FAILURE"); break;
case CompletionEvent.STATUS_SUCCESS: Log.e(TAG, "STATUS_SUCCESS "); break;
case CompletionEvent.STATUS_CANCELED: Log.e(TAG, "STATUS_CANCELED "); break;
}
event.dismiss();
}
}
I believe, you are falling into the same trap as many of us did before. I too originally assumed that the 'DriveEventService' takes care of notifications between multiple devices running under the same account. I tried and failed miserably, see here (and notice the resounding silence - since April 2014). I was always getting events on a single device only. So, I actually realized that Change Events work only locally within the GooPlaySvcs instance.
This was more or less confirmed by a comment from Steve Bazyl in this unrelated answer (please read including the 'ORIGINAL POST' paragraph), confirming my theory that both 'Change Events' and 'Completion Events' are local (Completion Events report result of network action - like http response).
So to answer your question. after fighting this for awhile, I had to develop a different strategy:
1/ perform GDAA action (create, update)
2/ wait for a Completion Event indicating your mod has been promoted to the Drive
3/ broadcast GCM message that include ResourceId (not DriveId !) plus optional data (up to 4K) to the registered participants.
4/ 'Registered participants' react to the message and download updated metadata/content, resolving the conflicts.
This solution is from summer 2014 and there may be some other pre-packaged solutions from Google since. I'd be happy myself to hear from people who know if there is more elegant solution.
Quite frankly, I don't understand what is this and this for, if the Completion Events do not timely reflect (notify of) the update from another device.
Good Luck
How to rectify the error "The method getErrorCodeName() is undefined for the type MulticastResult" ? I used following code
MulticastResult result = sender.send(message, androidTargets, 1);
if (result.getResults() != null) {
int canonicalRegId = result.getCanonicalIds();
if (canonicalRegId != 0) {
// same device has more than on registration ID: update database
}
} else {
int error = result.getFailure();
System.out.println("Broadcast failure: " + error);
String error_code_name = result.getErrorCodeName(); //Error is Here
if (error_code_name.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister database or remove from database
}
}
Thanks in advance !
You want to do this:
result.getResults().get(0).getErrorCodeName();
or something like:
for(Result r : result.getResults()){
if (Constants.ERROR_NOT_REGISTERED.equals(r)) {
// application has been removed from device - unregister database or remove from database
}
}
API docs:
http://developer.android.com/reference/com/google/android/gcm/server/MulticastResult.html
http://developer.android.com/reference/com/google/android/gcm/server/Result.html