/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.impl.android;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.PowerManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.widget.Toast;
import com.codename1.payment.Product;
import com.codename1.payment.PurchaseCallback;
import com.codename1.payment.Receipt;
import com.codename1.payments.v3.IabException;
import com.codename1.payments.v3.IabHelper;
import com.codename1.payments.v3.IabResult;
import com.codename1.payments.v3.Inventory;
import com.codename1.payments.v3.Purchase;
import com.codename1.payments.v3.SkuDetails;
import com.codename1.ui.Command;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Image;
import com.codename1.ui.events.ActionEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
public class CodenameOneActivity extends Activity {
private Menu menu;
private boolean nativeMenu = false;
private IntentResultListener intentResultListener;
private IntentResultListener defaultResultListener;
private boolean waitingForResult;
private boolean background;
private Vector intentResult = new Vector();
boolean requestForPermission = false;
//private final Object lock = new Object();
private Inventory inventory;
IabHelper mHelper;
// 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) {
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
if (result.isFailure()) {
return;
}
List ownedItems = inventory.getAllOwnedSkus();
for (Iterator iterator = ownedItems.iterator(); iterator.hasNext();) {
String sku = (String)iterator.next();
if (!isConsumable(sku)) {
continue;
}
//if the client own consumable products they need to be consumed
Purchase pur = inventory.getPurchase(sku);
if(pur.getItemType().equals(IabHelper.ITEM_TYPE_INAPP)){
mHelper.consumeAsync(pur, mConsumeFinishedListener);
}
}
CodenameOneActivity.this.inventory = inventory;
}
};
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(final IabResult result, final String sku, final Purchase purchase) {
// if we were disposed of in the meantime, quit.
if (mHelper == null) {
return;
}
final PurchaseCallback pc = getPurchaseCallback();
if(result.isFailure()){
if (pc != null) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
pc.itemPurchaseError(sku, result.getMessage());
}
});
return;
}
}
if (!verifyDeveloperPayload(purchase)) {
return;
}
if(result.isSuccess()){
if (pc != null) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
// Sandbox transactions have no order ID, so we'll make a dummy transaction ID
// in this case.
String transactionId = (purchase.getOrderId() == null || purchase.getOrderId().isEmpty()) ?
"play-sandbox-"+UUID.randomUUID().toString() : purchase.getOrderId();
String purchaseJsonStr = purchase.getOriginalJson();
try {
// In order to verify receipts, we'll need both the order data and the signature
// so we'll pack it all into a single JSON string.
JSONObject purchaseJson = new JSONObject(purchaseJsonStr);
JSONObject rootJson = new JSONObject();
rootJson.put("data", purchaseJson);
rootJson.put("signature", purchase.getSignature());
purchaseJsonStr = rootJson.toString();
} catch (JSONException ex) {
Logger.getLogger(CodenameOneActivity.class.getName()).log(Level.SEVERE, null, ex);
}
com.codename1.payment.Purchase.postReceipt(Receipt.STORE_CODE_PLAY, sku, transactionId, purchase.getPurchaseTime(), purchaseJsonStr);
pc.itemPurchased(sku);
}
});
inventory.addPurchase(purchase);
//This is a temp hack to get the last purchase raw data
//The IAP API needs to be modified to support this on all platforms
Display.getInstance().setProperty("lastPurchaseData", purchase.getOriginalJson());
}
}
//check if this product is a non consumable product
if (!isConsumable(sku)) {
return;
}
if(purchase.getItemType().equals(IabHelper.ITEM_TYPE_INAPP)){
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
}
}
};
// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(final Purchase purchase, final IabResult result) {
// if we were disposed of in the meantime, quit.
if (mHelper == null) {
return;
}
if (result.isFailure()) {
final PurchaseCallback pc = getPurchaseCallback();
if (pc != null) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
String sku = null;
if(purchase != null){
sku = purchase.getSku();
}
pc.itemPurchaseError(sku, result.getMessage());
}
});
}
}
if(purchase != null){
inventory.erasePurchase(purchase.getSku());
}
}
};
private PowerManager.WakeLock wakeLock;
/**
* Overriden by stub, returns the user application instance.
*/
protected Object getApp() {
return null;
}
boolean wasPurchased(String item) {
if(inventory != null){
return inventory.hasPurchase(item);
}
Display.getInstance().invokeAndBlock(new Runnable() {
@Override
public void run() {
while(inventory == null){
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
}
}
}
});
return inventory.hasPurchase(item);
}
void purchase(final String item) {
//waitingForResult = true;
this.runOnUiThread(new Runnable() {
@Override
public void run() {
mHelper.launchPurchaseFlow(CodenameOneActivity.this, item, IntentResultListener.PAYMENT,
mPurchaseFinishedListener, getPayload());
}
});
}
void subscribe(final String item) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
mHelper.launchPurchaseFlow(CodenameOneActivity.this,
item, IabHelper.ITEM_TYPE_SUBS,
IntentResultListener.PAYMENT, mPurchaseFinishedListener, getPayload());
}
});
}
public PurchaseCallback getPurchaseCallback() {
Object app = getApp();
PurchaseCallback pc = app instanceof PurchaseCallback ? (PurchaseCallback) app : null;
return pc;
}
@Override
protected void onResume() {
super.onResume();
AndroidImplementation.setActivity(this);
AndroidNativeUtil.onResume();
background = false;
}
/**
* Overriden by subclasses to return true if billing is supported on this
* build
*
* @return false
*/
protected boolean isBillingEnabled() {
return false;
}
/**
* Get the Android native Menu
*
* @return the Android Menu Object
*/
public Menu getMenu() {
return menu;
}
/**
* This method will enable the Android native Menu system instead of the
* regular Form Menu.
*
* @param enable
*/
public void enableNativeMenu(boolean enable) {
nativeMenu = enable;
}
@Override
public void onBackPressed() {
Display.getInstance().keyPressed(AndroidImplementation.DROID_IMPL_KEY_BACK);
Display.getInstance().keyReleased(AndroidImplementation.DROID_IMPL_KEY_BACK);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidImplementation.setActivity(this);
AndroidNativeUtil.onCreate(savedInstanceState);
if (android.os.Build.VERSION.SDK_INT >= 11) {
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
getActionBar().hide();
}
try {
if (isBillingEnabled()) {
String k = getBase64EncodedPublicKey();
if(k.length() == 0){
Log.e("Codename One", "android.licenseKey base64 is not configured");
}
mHelper = new IabHelper(this, getBase64EncodedPublicKey());
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.e("Codename One", "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.
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
} catch (Throwable t) {
// might happen if billing permissions are missing
System.out.print("This exception is totally valid and here only for debugging purposes");
t.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
AndroidImplementation.clearAppArg();
super.onStop();
background = true;
unlockScreen();
}
@Override
protected void onDestroy() {
super.onDestroy();
AndroidNativeUtil.onDestroy();
if (isBillingEnabled()) {
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
unlockScreen();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
this.menu = menu;
// By returning true we signal let Android know that we want the menu
// to be displayed
return nativeMenu && Display.isInitialized() && Display.getInstance().getCurrent() != null;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
AndroidNativeUtil.onSaveInstanceState(outState);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onLowMemory() {
super.onLowMemory();
AndroidNativeUtil.onLowMemory();
}
@Override
protected void onPause() {
if (InPlaceEditView.isEditing()) {
AndroidImplementation.stopEditing(true);
}
super.onPause();
AndroidNativeUtil.onPause();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
try {
Form currentForm = Display.getInstance().getCurrent();
if (currentForm == null) {
return false;
}
int numCommands = currentForm.getCommandCount();
// If there are no commands, there's nothing to put in the menu
if (numCommands == 0) {
return false;
}
// Build menu items from commands
for (int n = 0; n < numCommands; n++) {
Command command = currentForm.getCommand(n);
if (command != null) {
String txt = currentForm.getUIManager().localize(command.getCommandName(), command.getCommandName());
MenuItem item = menu.add(Menu.NONE, n, Menu.NONE, txt);
Image icon = command.getIcon();
if (icon != null) {
Bitmap b = (Bitmap) icon.getImage();
// Using BitmapDrawable with resources, to use device density (from 1.6 and above).
BitmapDrawable d = new BitmapDrawable(getResources(), b);
item.setIcon(d);
}
if (!command.isEnabled()) {
item.setEnabled(false);
}
if (android.os.Build.VERSION.SDK_INT >= 11 && command.getClientProperty("android:showAsAction") != null) {
String androidShowAsAction = command.getClientProperty("android:showAsAction").toString();
// From https://developer.android.com/guide/topics/resources/menu-resource.html
// "ifRoom" | "never" | "withText" | "always" | "collapseActionView"
if (androidShowAsAction.equalsIgnoreCase("ifRoom")) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (androidShowAsAction.equalsIgnoreCase("never")) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
} else if (androidShowAsAction.equalsIgnoreCase("withText")) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
} else if (androidShowAsAction.equalsIgnoreCase("always")) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else if (android.os.Build.VERSION.SDK_INT >= 14 && androidShowAsAction.equalsIgnoreCase("collapseActionView")) {
item.setShowAsAction(8); //MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
}
}
}
}
} catch (Throwable t) {
}
return nativeMenu;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
final Form currentForm = Display.getInstance().getCurrent();
if (currentForm == null) {
return false;
}
Command cmd = null;
final boolean[] tmpProp = new boolean[1];
if (item.getItemId() == android.R.id.home) {
cmd = currentForm.getBackCommand();
if (cmd == null) {
return false;
}
cmd.putClientProperty("source", "ActionBar");
tmpProp[0] = true;
}
int commandIndex = item.getItemId();
if (cmd == null) {
cmd = currentForm.getCommand(commandIndex);
}
final Command command = cmd;
final ActionEvent actionEvent = new ActionEvent(command);
//stop edit if the keybaord is open
AndroidImplementation.stopEditing();
// Protect ourselves from commands that misbehave. A crash here will crash the entire application
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
try {
currentForm.dispatchCommand(command, actionEvent);
//remove the temp source property
if (tmpProp[0]) {
command.putClientProperty("source", null);
}
} catch (Throwable e) {
Log.e("CodenameOneActivity.onOptionsItemSelected", e.toString() + Log.getStackTraceString(e));
}
}
});
return true;
}
protected void fireIntentResult() {
if (intentResult.size() > 0) {
final IntentResult response = (IntentResult) intentResult.get(0);
if (intentResultListener != null && response != null) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
intentResultListener.onActivityResult(response.getRequestCode(),
response.getResultCode(),
response.getData());
}
});
}
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//is this a payment result
if (mHelper != null && mHelper.handleActivityResult(requestCode, resultCode, data)) {
return;
}
IntentResult response = new IntentResult(requestCode, resultCode, data);
intentResult.add(response);
}
public void setIntentResultListener(IntentResultListener l) {
//if the activity is waiting for result don't override the intent listener
if(waitingForResult){
return;
}
this.intentResultListener = l;
if (l != null && l != defaultResultListener) {
waitingForResult = true;
}
}
public void setDefaultIntentResultListener(IntentResultListener l) {
this.defaultResultListener = l;
}
public void restoreIntentResultListener() {
waitingForResult = false;
setIntentResultListener(defaultResultListener);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
Bundle extra = intent.getExtras();
if(extra != null && extra.containsKey("WaitForResult") && !extra.getBoolean("WaitForResult")){
waitingForResult = false;
}else{
waitingForResult = true;
}
intentResult = new Vector();
if (InPlaceEditView.isEditing()) {
AndroidImplementation.stopEditing(true);
}
super.startActivityForResult(intent, requestCode);
}
@Override
public void startActivity(Intent intent) {
Bundle extra = intent.getExtras();
if(extra != null && extra.containsKey("WaitForResult") && !extra.getBoolean("WaitForResult")){
waitingForResult = false;
}else{
waitingForResult = true;
}
if (InPlaceEditView.isEditing()) {
AndroidImplementation.stopEditing(true);
}
super.startActivity(intent);
}
public boolean isWaitingForResult() {
return waitingForResult;
}
protected void setWaitingForResult(boolean waitingForResult) {
this.waitingForResult = waitingForResult;
}
public boolean isBackground() {
return background;
}
public void registerForPush(String key) {
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.setPackage("com.google.android.gms");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra("sender", key);
startService(registrationIntent);
}
public void stopReceivingPush() {
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregIntent.setPackage("com.google.android.gms");
unregIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
startService(unregIntent);
}
public void lockScreen() {
unlockScreen();
try {
android.os.PowerManager pm = (android.os.PowerManager) getSystemService(android.content.Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(android.os.PowerManager.SCREEN_BRIGHT_WAKE_LOCK | android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP | android.os.PowerManager.ON_AFTER_RELEASE, "Codename One");
} catch (Exception excp) {
excp.printStackTrace();
}
if (wakeLock != null) {
wakeLock.acquire();
}
}
public void unlockScreen() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
wakeLock = null;
}
}
public String getBase64EncodedPublicKey() {
String key = Display.getInstance().getProperty("android.licenseKey", "");
return key;
}
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
String getPayload() {
return "";
}
Product[] getProducts(String[] skus){
return getProducts(skus, false);
}
Product[] getProducts(String[] skus, boolean fromCacheOnly){
if(inventory != null){
ArrayList pList = new ArrayList<Product>();
ArrayList moreskusList = new ArrayList<Product>();
for (int i = 0; i < skus.length; i++) {
String sku = skus[i];
if(inventory.hasDetails(sku)){
SkuDetails details = inventory.getSkuDetails(sku);
Product p = new Product();
p.setSku(sku);
p.setDescription(details.getDescription());
p.setDisplayName(details.getTitle());
p.setLocalizedPrice(details.getPrice());
pList.add(p);
}else{
moreskusList.add(sku);
}
}
//if the inventory does not all the requestes sku make an update.
if(moreskusList.size() > 0 && !fromCacheOnly){
try {
inventory = mHelper.queryInventory(true, moreskusList);
return getProducts(skus, true);
} catch (IabException ex) {
Logger.getLogger(CodenameOneActivity.class.getName()).log(Level.SEVERE, null, ex);
}
}
Product [] products = new Product[pList.size()];
products = (Product[]) pList.toArray(products);
return products;
}
return null;
}
public boolean isConsumable(String sku){
if (sku.endsWith("nonconsume")) {
return false;
}
return true;
}
public void setRequestForPermission(boolean requestForPermission) {
this.requestForPermission = requestForPermission;
}
public boolean isRequestForPermission() {
return requestForPermission;
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if(grantResults != null || grantResults.length == 0) {
requestForPermission = false;
return;
}
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i("Codename One", "PERMISSION_GRANTED");
} else {
// Permission Denied
Toast.makeText(this, "Permission is denied", Toast.LENGTH_SHORT).show();
}
requestForPermission = false;
}
public boolean hasUI(){
return true;
}
}