I am trying to implement a remote volume control. It already works to control the volume with the hardware volume keys but when I try to move the slider in the MediaSession remote volume overlay, the VolumeProviderCompat.onAdjustVolume(..) callback is not called. I also tried other callbacks like MediaSessionCompat.Callback.onMediaButtonEvent(..) or VolumeProviderCompat.onSetVolumeTo(..) but they are not called at all.
If you don't know what I mean with the "MediaSession remote volume overlay", here is a screenshot:
I created a demo project which you can download here: https://github.com/SaschaZ/VolumeProviderDemo.
Here are the related parts of my DemoActivity:
public class DemoActivity extends AppCompatActivity {
...
private Notification createNotification(#NonNull final DemoVolumeController demoVolumeController) {
Log.d(TAG, "createNotification()");
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (session != null) {
session.release();
}
session = new MediaSessionCompat(this, "demoMediaSession");
session.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 1, 1.0f)
.build());
session.setPlaybackToRemote(createVolumeProvider(demoVolumeController));
session.setActive(true);
}
return builder.build();
}
private VolumeProviderCompat createVolumeProvider(#NonNull final DemoVolumeController demoVolumeController) {
// I don't use this callback directly, but I need to set it or my VolumeProvider will not work. (sounds
// strange but I tried it several times)
session.setCallback(new MediaSessionCompat.Callback() {
#Override
public boolean onMediaButtonEvent(final Intent mediaButtonEvent) {
Log.d(TAG, "onMediaButtonEvent() called with: " + "mediaButtonEvent = [" + mediaButtonEvent + "]");
return super.onMediaButtonEvent(mediaButtonEvent);
}
});
return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE,
100,
demoVolumeController.getVolume()) {
#Override
public void onAdjustVolume(final int direction) {
final int volume = demoVolumeController.setVolumeRelative(direction);
showVolume(volume);
Log.d(TAG, "onAdjustVolume() called with: " + "direction = [" + direction + "] - " +
"new volume=" + volume);
// Nasty hack to get sync with the volume overlay of Android. setCurrentVolume does not work :(
session.setPlaybackToRemote(createVolumeProvider(demoVolumeController));
}
};
}
...
}
Any hints?
Thank you in advance!
Using the Activity’s setVolumeControlStream method—typically within its onCreate method—
allows you to specify which audio stream should be controlled by the volume keys while the current
Activity is active:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.audioplayer);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
You can specify any of the available audio streams, but when using the Media Player, you should
specify the STREAM_MUSIC stream to make it the focus of the volume keys.
Related
From the Flutter side, using the PlatformChannel, I am navigating to an Android Java activity, and doing some processes.
The activity successfully opens and I'm able to do the functionality and have the final result of it.
How may I navigate back to the Flutter side to a specific page and pass a value?
P.S.: without going back to the same page and then redirecting to the
next page.
On the Flutter side:
I have these variables
/// Filters Method Channel
final filtersChannel = const MethodChannel('flutter.native/filters');
/// Filters Method Channel
final filtersResultChannel = const MethodChannel("flutter.native/result_filters");
I have a floatingActionButton with this function which invokes a MethodChannel
Future<void> startNewActivity() async {
try {
await filtersChannel.invokeMethod('open_filters');
} on PlatformException catch (e) {
debugPrint("Failed to Invoke: '${e.message}'.");
}
}
On the MainActivity.java
On the protected void onCreate(#Nullable Bundle savedInstanceState) function, I'm starting an activity which has the AR video recording like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
}
On the FiltersActivity.java
On the public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) function
I’m defining and invoking my two channels:
The flutter.native/result_filters channel which builds the UI and
the functionality.
The flutter.native/filters channel which returns the final result.
Here:
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
String resultFiltersChannelIdentifier = "flutter.native/result_filters";
filtersResultChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), resultFiltersChannelIdentifier);
String filtersChannelIdentifier = "flutter.native/filters";
MethodChannel filtersChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), filtersChannelIdentifier);
filtersChannel.setMethodCallHandler(this::filtersMethodCallHandler);
}
Then, the flutter.native/filters displays the UI using the filtersMethodCallHandler function. Here:
private void filtersMethodCallHandler(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("open_filters")) {
openUI();
} else {
result.notImplemented();
}
}
In the openUI function, I'm assigning the record button a function, here:
recordButton.setOnClickListener(this::toggleRecording);
And here's the toggleRecording function:
public void toggleRecording(View unusedView) {
boolean recording = videoRecorder.onToggleRecord();
if (recording) {
recordButton.setImageResource(R.drawable.round_stop);
Toast.makeText(this, "Started Recording", Toast.LENGTH_SHORT).show();
} else {
recordButton.setImageResource(R.drawable.round_videocam);
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
videoPath = videoRecorder.getVideoPath().getAbsolutePath();
Toast.makeText(this, "Video saved: " + videoPath, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + videoPath);
// Send notification of updated content.
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.TITLE, "Sceneform Video");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoPath);
getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
filtersResultChannel.invokeMethod("filters_result", videoPath);
finish();
}
}
As shown above, I'm invoking the filters_result method for the filtersResultChannel channel and I'm adding the videoPath to it.
And then, I'm calling the finish(); method to close the FiltersActivity and return back to the MainAvtivity which successfully returns me to the Flutter page!
BACK to the Flutter side,
I'm listening to the filtersResultChannel like this:
#override
void initState() {
super.initState();
filtersResultChannel.setMethodCallHandler(_filtersResultHandler);
}
Future _filtersResultHandler(MethodCall methodCall) async {
if (methodCall.method == "filters_result") {
final videoPath = methodCall.arguments;
if (videoPath != null && videoPath.length >= 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
debugPrint("YES YES YES => $videoPath");
setState(() {
reportStatus = videoPath;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoShow(clipPath: videoPath),
),
);
});
});
}
return null;
} else {
return null;
}
}
As shown above, I have a debugPrint statement, this statement prints the returned videoPath from the filtersResultChannel
<--------->
THE PROBLEM
<--------->
Even though I'm successfully getting the videoPath value and successfully returning back to the Flutter page, I'm NOT able to use it!!
The setState(); doesn't update the UI NOR navigate to the next screen, the VideoShow screen!
HOW MAY I FIX SUCH AN ISSUE?
I am implementing Camera X. The issue i am facing is to implement a mechanism to lock/freeze camera preview when picture is captured. Currently i have implement a workaround but it doesn't work well if the flash light is on while capturing. I get a frame from previewView (PreviewView) previewView.getBitmap() as before capturing the image and then display in an captureImage (ImageView). But the the freeze frame not show flash light update. My current code is below
private void capturePhoto() {
showProgress(true);
// Get the Information to be used & stored with Image
ContentValues contentValues = getImageSaveInfo();
Uri externalUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions
.Builder(getContentResolver(), externalUri, contentValues)
.build();
// Play the Capture Sound when a picture is captured.
playCameraShutterSound();
// Display current frame From Preview in ImageView.
freezePreview(true);
imageCapture.takePicture(options,
ContextCompat.getMainExecutor(this),
new ImageCapture.OnImageSavedCallback() {
#Override
public void onImageSaved(#NonNull ImageCapture.OutputFileResults results) {
ToastUtility.successToast(getApplicationContext(),
"Photo Capture Successfully");
// Update Last Taken Image View with new Image
getLastTakenImage();
if (results.getSavedUri() != null) {
Log.d(TAG, "Image Saved At -> " + results.getSavedUri().toString());
}
showProgress(false);
freezePreview(false);
}
#Override
public void onError(#NonNull ImageCaptureException exception) {
ToastUtility.errorToast(getApplicationContext(),
"Photo Couldn't Capture");
Log.d(TAG, "Image Capture Error -> " + exception.getMessage());
showProgress(false);
freezePreview(false);
}
});
}
private void freezePreview(boolean value) {
if (value) {
Bitmap bitmap = mainBinding.previewView.getBitmap();
Glide.with(getApplicationContext())
.load(bitmap).into(mainBinding.captureImage);
mainBinding.captureImage.setVisibility(View.VISIBLE);
mainBinding.previewView.setVisibility(View.INVISIBLE);
} else {
mainBinding.previewView.setVisibility(View.VISIBLE);
mainBinding.captureImage.setVisibility(View.INVISIBLE);
}
}
The flash is triggered at some point after takePicture() is called, there isn't a callback for it in CameraX, so there isn't a direct way to know when it's fired.
You can instead use camera2 interop to indirectly check for the flash state. You can add a session CaptureCallback to ImageCapture's config, then inside the callback's onCaptureCompleted, check if the flash state of the total result is FIRED.
// Override onCaptureCompleted to check for the flash state
CameraCaptureSession.CaptureCallback sessionCaptureCallback = //... ;
// Initialize an ImageCapture builder
ImageCapture.Builder configBuilder = new ImageCapture.Builder();
// Add the session CaptureCallback to it
new Camera2Interop.Extender<>(configBuilder)
.setSessionCaptureCallback(sessionCaptureCallback);
// Build the ImageCapture use case
ImageCapture useCase = configBuilder.build();
I am trying to develop a simple app which will record the user's activity (accelerometer values) on a txt or csv file.
My app consists of 2 java classes MainActivity and MyService. The MainActivity includes two buttons to start and stop the service and the required permissions. However, the onSensorChanged normally logs for the first 3 minutes after locking the phone (turning off the screen) and then stops logging. As soon as I open the screen the logd starts working again. Same behavior for the records in txt file. I found out that the app seems to be working excellent if I override the battery optimizations. However, I need the phone to also be working in doze mode to save some battery drain. Has anyone else had a similar issue?
Here is my Foreground Service:
public class MyService extends Service implements SensorEventListener {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final String TAG = "MyService";
private Messenger messageHandler;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Context mContext;
private PowerManager.WakeLock wakeLock = null;
//private HandlerThread mSensorThread;
//private Handler mHandler;
#SuppressLint("MissingPermission")
#Override
public void onCreate() {
super.onCreate();
Log.v("shake service startup", "registering for shake");
mContext = getApplicationContext();
//mHandler = new Handler(mSensorThread.getLooper());
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Wakelock :: TAG");
// Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
// code be called whenever the phone enters standby mode.
//IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
//registerReceiver(mReceiver, filter);
}
/*
// BroadcastReceiver for handling ACTION_SCREEN_OFF.
public BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Check action just to be on the safe side.
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.v("shake mediator screen off","trying re-registration");
// Unregisters the listener and registers it again.
mSensorManager.unregisterListener(MyService.this);
mSensorManager.registerListener(MyService.this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
}
}
};
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
return START_STICKY;
//stopSelf();
//return START_NOT_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
#Override
public void onDestroy() {
super.onDestroy();
if(mSensorManager != null){
//noinspection MissingPermission
mSensorManager.unregisterListener(MyService.this);
}
//unregisterReceiver(mReceiver);
try{
wakeLock.release();//always release before acquiring for safety just in case
}
catch(Exception e){
//probably already released
Log.e(TAG, e.getMessage());
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onSensorChanged(SensorEvent event) {
Log.d(TAG, "onSensorChanged: " + event.timestamp + " " + event.values[0] + " " + event.values[1] + " " + event.values[2]);
recordAccelValues(String.valueOf(event.timestamp), event.values[0] + " " + event.values[1] + " " + event.values[2]);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private void recordAccelValues(String time, String accel_values) {
String record = time + " " + accel_values + "\n";
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (Build.VERSION.SDK_INT >= 23) {
File sdcard = Environment.getExternalStorageDirectory();
File dir = new File(sdcard.getAbsolutePath() + "/text/");
if(!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, "dailyRecordsAccel.dat");
FileOutputStream os = null;
try {
os = new FileOutputStream(file, true);
os.write(record.getBytes());
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
As you can see in the code I tried several recommendations from other questions I found, like wakelock and Intent.ACTION_SCREEN_OFF but they didn't seem to work.
Accelerometer stops delivering samples when the screen is off on Droid/Nexus One even with a WakeLock
The only one way to keep alive your service it's to avoid battery optimization for your application. Which is possible within two ways below. Please note! In both cases you will keep device alive, which means that device will never sleep (enter doze states obviously). It's whole point of device sleep, to avoid pending work of background services like yours.
Using Android WakeLocks, For ex. below.
val wakeLock: PowerManager.WakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager. FULL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire()
}
}
Changing setting to avoid battery optimization for specific app. As you mentioned in your question.
It is normal behavior. Android delete all proceses to save power. If you want do a job then ask user to keep screen on, else you can use AlarmManager only to call a Service (Intent, Reciver) do "small job" and go to sleep again.
I am trying to make an android application about localization, i am trying however to make a list of Bluetooth devises found by my smartphone and refresh that lists information every 10 seconds. I want to do that because I am also recording the signal strength (RSSI) and want to record when a beacon is getting out of range when moved or the signal is getting weaker. So refreshing the list will help with that. I have provided some of my code below.
#Override protected void onResume() {
super.onResume();
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));
}
private final BroadcastReceiver devicesFoundReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int rssid = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
listAdapter.add(device.getName() + "\n" + device.getAddress() + "\n" + " RSSI: " + rssid + "dBm");
listAdapter.notifyDataSetChanged();
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
scanningButton.setText("Scan Bluetooth Devices");
}else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)){
scanningButton.setText("Scanning in progress ...");
}
}
};
use a listview then call notifyDataSetChanged() on your Adapter object
I followed these instructions to integrate both Libgdx and native android code using ActionResolver interface. I have no problem calling the Android method from the Libgdx part of my code. But I am hitting a dead end when I am trying to intergrate Google IAP with Libgdx. According to TrivialDrive example, it uses mPurchaseFinishedListener (outside of calling method).
My question is: how do I pass this IAP resultcode back to Libgdx since the listener is outside the calling method? Currently, purchase process went through, but the libgdx part of my code is not being "informed" of the purchase status/result.
This is my code:
Any help is much appreciated.
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product);
}
MainActivity:
public class MainActivity extends AndroidApplication implements IActionResolver {
// Debug tag, for logging
static final String TAG = "greatgame";
// Does the user have the premium upgrade?
boolean mIsUpgraded = false;
// SKUs for our products: the cat, all, or pow
static final String SKU_UPGRADE = "android.test.purchased";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// The helper object
IabHelper mHelper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
initialize(new Catland(this), cfg);
}
void iAbStartup() {
String base64EncodedPublicKey = "some key";
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// Is it a failure?
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
// Do we have the SKU_UPGRADE upgrade?
Purchase thisUpgrade = inventory.getPurchase(SKU_UPGRADE);
mIsUpgraded = (thisUpgrade != null && verifyDeveloperPayload(thisUpgrade));
Log.d(TAG, "User is " + (mIsUpgraded ? "Upgraded" : "Free"));
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
runPurchaseFlow(submitProduct);
}
};
// Run real purchase flow
public void runPurchaseFlow(int product) {
Log.d(TAG, "runPurchaseFlow");
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
* an empty string, but on a production app you should carefully generate this. */
String payload = "";
if (product == 1)
mHelper.launchPurchaseFlow(this, SKU_UPGRADE, RC_REQUEST, mPurchaseFinishedListener, payload);
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d(TAG, "Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_CAT)) {
// bought the upgrade!
Log.d(TAG, "Purchase Upgrade. Congratulating user.");
mIsUpgraded = true;
// how do i pass this result to the libgdx?
}
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
#Override
public int requestIabPurchase(int product) {
iAbStartup();
return 0; // how do i get the result from mPurchaseFinishedListener?
}
}
PurchaseScreen
result = greatgame.actionResolver.requestIabPurchase(1);
You won't be able to return the result from requestIabPurchase() - the only methods of doing so would block for a long time. The best way, in my opinion, would be to create a listener interface of your own that your LibGdx project implements, and pass that into your request interface. For example:
In your libGdx project somewhere:
interface PurchaseCallback {
public int setPurchaseResult(int result);
}
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product, PurchaseCallback callback);
}
In PurchaseScreen, implement PurchaseCallback:
#override
public int setPurchaseResult(int result) {
// Yay! I have a result from a purchase! Maybe you want a boolean instead of an int? I don't know. Maybe an int (for the product code) and a boolean.
}
...and pass whatever is implementing PurchaseCallback (I'm assuming your PurchaseScreen does itself):
result = greatgame.actionResolver.requestIabPurchase(1, this);
Finally, hook it all up in MainActivity:
PurchaseCallback mCallback = null;
mPurchaseFinishedListener = ... etc. etc.
.
.
.
if (mCallback != null) {
mCallback.setPurchaseResult(0);
}
.
.
.
#Override
public int requestIabPurchase(int product, PurchaseCallback callback) {
mCallback = callback; // save this for later
iAbStartup();
return 0;
}
Note that you should call PurchaseCallback.setPurchaseResult() everywhere that mPurchaseFinishedListener has return, not only at the line // how do i pass this result to the libgdx? - otherwise, you will never know if a purchase failed or is just taking a really long time.