Removed trial mode (#19)
Co-authored-by: SVolf <a.zhiganov@mintmail.ru>
|
@ -85,15 +85,7 @@ android {
|
|||
manifestPlaceholders += [app_name: "LLAppExtreme"]
|
||||
applicationId "net.pierrox.lightning_launcher_extreme"
|
||||
dimension "version"
|
||||
buildConfigField "boolean", "IS_TRIAL", "false"
|
||||
}
|
||||
// trial debug app fails to build, disabled until fixed (or deleted)
|
||||
// trial {
|
||||
// manifestPlaceholders += [app_name: "LLAppTrial"]
|
||||
// applicationId "net.pierrox.lightning_launcher"
|
||||
// dimension "version"
|
||||
// buildConfigField "boolean", "IS_TRIAL", "true"
|
||||
// }
|
||||
}
|
||||
buildFeatures {
|
||||
aidl true
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
<provider android:name="net.pierrox.lightning_launcher.util.ApiProvider" android:authorities="net.pierrox.lightning_launcher_extreme.api"
|
||||
android:exported="true" />
|
||||
|
||||
<activity android:name="net.pierrox.lightning_launcher.PurchaseProcess"
|
||||
android:theme="@style/AppDialog"/>
|
||||
|
||||
<activity
|
||||
android:name="net.pierrox.lightning_launcher.LWPSettings"
|
||||
android:exported="true"
|
||||
|
|
|
@ -1,352 +0,0 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pierre Hébert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Process;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.vending.licensing.LicenseChecker;
|
||||
import com.google.android.vending.licensing.LicenseCheckerCallback;
|
||||
import com.google.android.vending.licensing.Policy;
|
||||
import com.google.android.vending.licensing.ServerManagedPolicy;
|
||||
|
||||
import net.pierrox.lightning_launcher.data.FileUtils;
|
||||
import net.pierrox.lightning_launcher.data.Page;
|
||||
import net.pierrox.lightning_launcher.engine.LightningEngine;
|
||||
import net.pierrox.lightning_launcher.iab.IabHelper;
|
||||
import net.pierrox.lightning_launcher.iab.IabResult;
|
||||
import net.pierrox.lightning_launcher.iab.Inventory;
|
||||
import net.pierrox.lightning_launcher.iab.Purchase;
|
||||
import net.pierrox.lightning_launcher.prefs.LLPreference;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
public class LLAppExtreme extends LLAppPhone {
|
||||
private static final String key1 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA";
|
||||
private static final String key2 = "5FKITc94MjDMThUW1wOMqqt/m0TZnAu5spcmrEol6qGuNz";
|
||||
private static final String key3 = "m4/";
|
||||
private static final String key4 = "hI3T9SPxlX4faIxSX0hwLJAtbb5IZWX5XvuQdQovF9W9";
|
||||
private static final String key5 = "vRdURFT6D7K01k+doWbMDZnbfQXiYKHaaBja+SlsZA4UsHF6RubVTi+nOET1xBlpjNwQ6wl69GdM+y8WA1WR47JBNph6wuCF0q7pz2KbuBDvh5vSvYaBGb9dflqnOKy2S47DSA7HOwffTUtxilskp";
|
||||
private static final String key6 = "JvKKBdyKwQoNTKyp7bjXUrFg/tlJOTo0je4RkcvBHiYCW/yEQKSPY43nlnapcy6L4P+0IV+GDHI+Zx1D+mPo6BmsTwIDAQAB";
|
||||
private static final String LWP_PKG = "net.pierrox.lightning_launcher.lwp_key";
|
||||
private static final String PATH_TEST = "t";
|
||||
private static final String COLUMN_IS_LICENSED = "l";
|
||||
private LicenseCheckerCallback mLicenseCheckerCallback;
|
||||
private LicenseChecker mChecker;
|
||||
private boolean mIsLicensed = true;
|
||||
private IabHelper mIABHelper;
|
||||
private String mIabKey;
|
||||
private boolean mHasLWPIab;
|
||||
private boolean mHasLWPKey;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// obfuscation
|
||||
String three = String.valueOf((new Date().getYear() + 2901) / 1000);
|
||||
mIabKey = key1 + three + key2;
|
||||
|
||||
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
|
||||
|
||||
mIabKey += three + key3 + three + key4 + getString(R.string.internal_version);
|
||||
|
||||
// obfuscation
|
||||
mIabKey += key5 + three + key6;
|
||||
|
||||
// Construct the LicenseChecker with a Policy.
|
||||
mChecker = new LicenseChecker(
|
||||
this, new ServerManagedPolicy(this),
|
||||
mIabKey // Your public licensing key.
|
||||
);
|
||||
|
||||
checkLicense();
|
||||
|
||||
checkLwpKey();
|
||||
|
||||
setupIAB();
|
||||
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
|
||||
mChecker.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreeVersion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersion() {
|
||||
return !mIsLicensed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersionExpired() {
|
||||
return !mIsLicensed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTrialLeft() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View managePreferenceViewLockedFlag(LLPreference preference, View preference_view) {
|
||||
return preference_view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageAddItemDialogLockedFlag(View add_item_view, boolean locked) {
|
||||
// pass
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFeatureLockedDialog(Context context) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Version.APP_STORE_INSTALL_PREFIX + context.getPackageName()));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// pass
|
||||
}
|
||||
Toast.makeText(context, "Couldn't validate the Play Store license. Please check your internet connectivity.", Toast.LENGTH_LONG).show();
|
||||
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUnlockProcess(Context context) {
|
||||
// pass
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installPromotionalIcons(Page dashboard) {
|
||||
// pass
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkLicense() {
|
||||
mChecker.checkAccess(mLicenseCheckerCallback);
|
||||
}
|
||||
|
||||
public String getIabKey() {
|
||||
return mIabKey;
|
||||
}
|
||||
|
||||
private void setupIAB() {
|
||||
readStoredUnlockedStatus();
|
||||
|
||||
mIABHelper = new IabHelper(this, getIabKey());
|
||||
mIABHelper.enableDebugLogging(BuildConfig.DEBUG);
|
||||
mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
|
||||
public void onIabSetupFinished(IabResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Have we been disposed of in the meantime? If so, quit.
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
checkProducts(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void checkLwpKey() {
|
||||
// first step : the permission is granted meaning the package is installed
|
||||
mHasLWPKey = checkPermission(LWP_PKG, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
// second step, ask the key to check its license
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
boolean hasLicensedKey = false;
|
||||
try {
|
||||
Cursor c = getContentResolver().query(Uri.parse("content://" + LWP_PKG + "/" + PATH_TEST), new String[]{COLUMN_IS_LICENSED}, null, null, null);
|
||||
if (c != null) {
|
||||
c.moveToNext();
|
||||
hasLicensedKey = c.getInt(0) == 1;
|
||||
c.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// pass
|
||||
}
|
||||
return hasLicensedKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean hasLicensedKey) {
|
||||
// key is installed, but no license
|
||||
if (mHasLWPKey && !hasLicensedKey) {
|
||||
LightningEngine engine = getAppEngine();
|
||||
engine.getGlobalConfig().lwpScreen = Page.NONE;
|
||||
engine.notifyGlobalConfigChanged();
|
||||
}
|
||||
mHasLWPKey = hasLicensedKey;
|
||||
}
|
||||
}.execute((Void) null);
|
||||
|
||||
}
|
||||
|
||||
private File getUnlockInfoDataFile() {
|
||||
return new File(getFilesDir(), "products");
|
||||
}
|
||||
|
||||
private void readStoredUnlockedStatus() {
|
||||
File data = getUnlockInfoDataFile();
|
||||
|
||||
mHasLWPIab = false;
|
||||
|
||||
if (data.exists()) {
|
||||
JSONObject o = FileUtils.readJSONObjectFromFile(data);
|
||||
if (o != null) {
|
||||
try {
|
||||
mHasLWPIab = o.getBoolean(getString(R.string.iab_lwp));
|
||||
} catch (JSONException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setProductStatus(String sku, boolean purchased) {
|
||||
if (sku.equals(getString(R.string.iab_lwp))) {
|
||||
mHasLWPIab = purchased;
|
||||
}
|
||||
File data = getUnlockInfoDataFile();
|
||||
JSONObject o = new JSONObject();
|
||||
try {
|
||||
o.put(sku, purchased);
|
||||
FileUtils.saveStringToFile(o.toString(), data);
|
||||
} catch (Exception e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
public void checkProducts(final UnlockResultReceiver receiver) {
|
||||
// Have we been disposed of in the meantime? If so, quit.
|
||||
if (mIABHelper == null || !mIABHelper.isSetupDone()) return;
|
||||
|
||||
ArrayList<String> skus = new ArrayList<>(1);
|
||||
final String iab_lwp = getString(R.string.iab_lwp);
|
||||
skus.add(iab_lwp);
|
||||
|
||||
mIABHelper.queryInventoryAsync(true, skus, new IabHelper.QueryInventoryFinishedListener() {
|
||||
@Override
|
||||
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
|
||||
if (result.isFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Purchase lwp = inventory.getPurchase(iab_lwp);
|
||||
boolean has_lwp = lwp != null /*&& verifyDeveloperPayload(unlock_pro)*/;
|
||||
if (has_lwp) {
|
||||
setProductStatus(iab_lwp, true);
|
||||
if (receiver != null) receiver.setUnlocked(iab_lwp, true);
|
||||
} else {
|
||||
setProductStatus(iab_lwp, false);
|
||||
if (receiver != null) receiver.setUnlocked(iab_lwp, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean hasLWP() {
|
||||
return mHasLWPIab || mHasLWPKey;
|
||||
}
|
||||
|
||||
public void showProductLockedDialog(final Context context, int title, int message, final String sku) {
|
||||
// need an activity context to add a window
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(context.getString(title));
|
||||
builder.setMessage(context.getString(message));
|
||||
builder.setPositiveButton(context.getString(R.string.iab_y_key), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
context.startActivity(Intent.createChooser(new Intent(Intent.ACTION_VIEW, Uri.parse(Version.APP_STORE_INSTALL_PREFIX + "net.pierrox.lightning_launcher.lwp_key")), ""));
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(context.getString(R.string.iab_y_app), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startPurchaseProcess(context, sku);
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton(context.getString(R.string.iab_no), null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void startPurchaseProcess(Context context, String sku) {
|
||||
PurchaseProcess.startActivity(context, sku);
|
||||
}
|
||||
|
||||
public interface UnlockResultReceiver {
|
||||
void setUnlocked(String sku, boolean unlocked);
|
||||
}
|
||||
|
||||
private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
|
||||
public void allow(int reason) {
|
||||
mIsLicensed = true;
|
||||
}
|
||||
|
||||
public void dontAllow(int reason) {
|
||||
|
||||
// If the reason received from the policy is RETRY, it was probably
|
||||
// due to a loss of connection with the service, so we should give the
|
||||
// user a chance to retry. So show a dialog to retry.
|
||||
// Otherwise, the user is not licensed to use this app.
|
||||
// Your response should always inform the user that the application
|
||||
// is not licensed, but your behavior at that point can vary. You might
|
||||
// provide the user a limited access version of your app or you can
|
||||
// take them to Google Play to purchase the app.
|
||||
mIsLicensed = reason == Policy.RETRY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applicationError(int errorCode) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,41 +24,17 @@ SOFTWARE.
|
|||
|
||||
package net.pierrox.lightning_launcher;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
import net.pierrox.lightning_launcher.activities.ScreenManager;
|
||||
|
||||
/**
|
||||
* TODO: We need to refactor ScreenManager.getMode()
|
||||
* then remove this class, and pass screen modes directly trough an Intent
|
||||
**/
|
||||
public class LWPSettings extends ScreenManager {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
checkHasLwp();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMode() {
|
||||
return SCREEN_MODE_LWP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(checkHasLwp()) {
|
||||
super.onClick(v);
|
||||
}
|
||||
}
|
||||
|
||||
// return true if LWP is enabled, otherwise return false and display a dialog
|
||||
private boolean checkHasLwp() {
|
||||
LLAppExtreme app = (LLAppExtreme) LLApp.get();
|
||||
app.checkLwpKey();
|
||||
if(app.hasLWP()) {
|
||||
return true;
|
||||
} else {
|
||||
app.showProductLockedDialog(this, R.string.iab_ul_lwp_t, R.string.iab_ul_lwp_m, getString(R.string.iab_lwp));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pierre Hébert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import net.pierrox.lightning_launcher.iab.IabHelper;
|
||||
import net.pierrox.lightning_launcher.iab.IabResult;
|
||||
import net.pierrox.lightning_launcher.iab.Inventory;
|
||||
import net.pierrox.lightning_launcher.iab.Purchase;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PurchaseProcess extends AppCompatActivity {
|
||||
private static final int REQUEST_PURCHASE_UNLOCK = 1;
|
||||
private static final String INTENT_EXTRA_SKU = "sku";
|
||||
|
||||
private String mSku;
|
||||
|
||||
private IabHelper mIABHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSku = getIntent().getStringExtra(INTENT_EXTRA_SKU);
|
||||
|
||||
setContentView(R.layout.purchase_process);
|
||||
|
||||
((TextView)findViewById(R.id.au_msg)).setText(R.string.tr_eu);
|
||||
((TextView)findViewById(R.id.au_pw)).setText(R.string.tr_pw);
|
||||
findViewById(R.id.au_ok).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setWaitScreen(true, 0);
|
||||
|
||||
mIABHelper = new IabHelper(this, ((LLAppExtreme) LLApp.get()).getIabKey());
|
||||
mIABHelper.enableDebugLogging(BuildConfig.DEBUG);
|
||||
mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
|
||||
public void onIabSetupFinished(IabResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
// Have we been disposed of in the meantime? If so, quit.
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
|
||||
ArrayList<String> skus = new ArrayList<String>();
|
||||
skus.add(mSku);
|
||||
mIABHelper.queryInventoryAsync(true, skus, mGotInventoryListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (mIABHelper != null) {
|
||||
mIABHelper.dispose();
|
||||
mIABHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if(mIABHelper == null) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
if (!mIABHelper.handleActivityResult(requestCode, resultCode, data)) {
|
||||
// not handled, so handle it ourselves (here's where you'd
|
||||
// perform any handling of activity results not related to in-app
|
||||
// billing...
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void purchaseUnlock() {
|
||||
mIABHelper.launchPurchaseFlow(this, mSku, REQUEST_PURCHASE_UNLOCK, mPurchaseFinishedListener, "");
|
||||
}
|
||||
|
||||
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
|
||||
@Override
|
||||
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
// Is it a failure?
|
||||
if (result.isFailure()) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
Purchase unlock_pro = inventory.getPurchase(mSku);
|
||||
boolean unlocked_pro = unlock_pro != null && verifyDeveloperPayload(unlock_pro);
|
||||
if(unlocked_pro) {
|
||||
setUnlocked(true);
|
||||
setWaitScreen(false, R.string.tr_ty);
|
||||
} else {
|
||||
mIABHelper.launchPurchaseFlow(PurchaseProcess.this, mSku, REQUEST_PURCHASE_UNLOCK, mPurchaseFinishedListener);
|
||||
setUnlocked(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
|
||||
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
if (result.isFailure()) {
|
||||
setWaitScreen(false, result.getResponse() == IabHelper.IABHELPER_USER_CANCELLED ? R.string.tr_c : R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
if (!verifyDeveloperPayload(purchase)) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
final String sku = purchase.getSku();
|
||||
if (sku.equals(mSku)) {
|
||||
setUnlocked(true);
|
||||
setWaitScreen(false, R.string.tr_ty);
|
||||
} else {
|
||||
setUnlocked(false);
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void setWaitScreen(boolean on, int msg_res_id) {
|
||||
findViewById(R.id.au_d).setVisibility(on ? View.INVISIBLE : View.VISIBLE);
|
||||
findViewById(R.id.au_w).setVisibility(on ? View.VISIBLE : View.INVISIBLE);
|
||||
if(msg_res_id != 0) {
|
||||
((TextView)findViewById(R.id.au_msg)).setText(msg_res_id);
|
||||
}
|
||||
}
|
||||
|
||||
private void setUnlocked(boolean unlocked) {
|
||||
((LLAppExtreme)LLApp.get()).setProductStatus(mSku, unlocked);
|
||||
}
|
||||
|
||||
/** Verifies the developer payload of a purchase. */
|
||||
boolean verifyDeveloperPayload(Purchase p) {
|
||||
String payload = p.getDeveloperPayload();
|
||||
|
||||
/*
|
||||
* TODO: verify that the developer payload of the purchase is correct. It will be
|
||||
* the same one that you sent when initiating the purchase.
|
||||
*
|
||||
* WARNING: Locally generating a random string when starting a purchase and
|
||||
* verifying it here might seem like a good approach, but this will fail in the
|
||||
* case where the user purchases an item on one device and then uses your app on
|
||||
* a different device, because on the other device you will not have access to the
|
||||
* random string you originally generated.
|
||||
*
|
||||
* So a good developer payload has these characteristics:
|
||||
*
|
||||
* 1. If two different users purchase an item, the payload is different between them,
|
||||
* so that one user's purchase can't be replayed to another user.
|
||||
*
|
||||
* 2. The payload must be such that you can verify it even when the app wasn't the
|
||||
* one who initiated the purchase flow (so that items purchased by the user on
|
||||
* one device work on other devices owned by the user).
|
||||
*
|
||||
* Using your own server to store and verify developer payloads across app
|
||||
* installations is recommended.
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void startActivity(Context context, String sku) {
|
||||
Intent intent = new Intent(context, PurchaseProcess.class);
|
||||
intent.putExtra(INTENT_EXTRA_SKU, sku);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
|
@ -1,581 +0,0 @@
|
|||
// Portions copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
// This code was converted from code at http://iharder.sourceforge.net/base64/
|
||||
// Lots of extraneous features were removed.
|
||||
/* The original code said:
|
||||
* <p>
|
||||
* I am placing this code in the Public Domain. Do with it as you will.
|
||||
* This software comes with no guarantees or warranties but with
|
||||
* plenty of well-wishing instead!
|
||||
* Please visit
|
||||
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
|
||||
* periodically to check for updates or to contribute improvements.
|
||||
* </p>
|
||||
*
|
||||
* @author Robert Harder
|
||||
* @author rharder@usa.net
|
||||
* @version 1.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base64 converter class. This code is not a complete MIME encoder;
|
||||
* it simply converts binary data to base64 data and back.
|
||||
*
|
||||
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
|
||||
* class.
|
||||
*/
|
||||
public class Base64 {
|
||||
/**
|
||||
* Specify encoding (value is {@code true}).
|
||||
*/
|
||||
public final static boolean ENCODE = true;
|
||||
|
||||
/**
|
||||
* Specify decoding (value is {@code false}).
|
||||
*/
|
||||
public final static boolean DECODE = false;
|
||||
|
||||
/**
|
||||
* The equals sign (=) as a byte.
|
||||
*/
|
||||
private final static byte EQUALS_SIGN = (byte) '=';
|
||||
|
||||
/**
|
||||
* The new line character (\n) as a byte.
|
||||
*/
|
||||
private final static byte NEW_LINE = (byte) '\n';
|
||||
|
||||
/**
|
||||
* The 64 valid Base64 values.
|
||||
*/
|
||||
private final static byte[] ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '+', (byte) '/'};
|
||||
|
||||
/**
|
||||
* The 64 valid web safe Base64 values.
|
||||
*/
|
||||
private final static byte[] WEBSAFE_ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '-', (byte) '_'};
|
||||
|
||||
/**
|
||||
* Translates a Base64 value to either its 6-bit reconstruction value
|
||||
* or a negative number indicating some other meaning.
|
||||
**/
|
||||
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
|
||||
62, // Plus sign at decimal 43
|
||||
-9, -9, -9, // Decimal 44 - 46
|
||||
63, // Slash at decimal 47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
/**
|
||||
* The web safe decodabet
|
||||
*/
|
||||
private final static byte[] WEBSAFE_DECODABET =
|
||||
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
|
||||
62, // Dash '-' sign at decimal 45
|
||||
-9, -9, // Decimal 46-47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, // Decimal 91-94
|
||||
63, // Underscore '_' at decimal 95
|
||||
-9, // Decimal 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
// Indicates white space in encoding
|
||||
private final static byte WHITE_SPACE_ENC = -5;
|
||||
// Indicates equals sign in encoding
|
||||
private final static byte EQUALS_SIGN_ENC = -1;
|
||||
|
||||
/**
|
||||
* Defeats instantiation.
|
||||
*/
|
||||
private Base64() {
|
||||
}
|
||||
|
||||
/* ******** E N C O D I N G M E T H O D S ******** */
|
||||
|
||||
/**
|
||||
* Encodes up to three bytes of the array <var>source</var>
|
||||
* and writes the resulting four Base64 bytes to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 3 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 4 for
|
||||
* the <var>destination</var> array.
|
||||
* The actual number of significant bytes in your array is
|
||||
* given by <var>numSigBytes</var>.
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param numSigBytes the number of significant bytes in your array
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @return the <var>destination</var> array
|
||||
* @since 1.3
|
||||
*/
|
||||
private static byte[] encode3to4(byte[] source, int srcOffset,
|
||||
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901 Bit position
|
||||
// --------000000001111111122222222 Array position from threeBytes
|
||||
// --------| || || || | Six bit groups to index alphabet
|
||||
// >>18 >>12 >> 6 >> 0 Right shift necessary
|
||||
// 0x3f 0x3f 0x3f Additional AND
|
||||
|
||||
// Create buffer with zero-padding if there are only one or two
|
||||
// significant bytes passed in the array.
|
||||
// We have to shift left 24 in order to flush out the 1's that appear
|
||||
// when Java treats a value as negative that is cast from a byte to an int.
|
||||
int inBuff =
|
||||
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
|
||||
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
|
||||
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
|
||||
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
|
||||
return destination;
|
||||
case 2:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
case 1:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = EQUALS_SIGN;
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
default:
|
||||
return destination;
|
||||
} // end switch
|
||||
} // end encode3to4
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
* Equivalent to calling
|
||||
* {@code encodeBytes(source, 0, source.length)}
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return encode(source, 0, source.length, ALPHABET, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into web safe Base64 notation.
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
*/
|
||||
public static String encodeWebSafe(byte[] source, boolean doPadding) {
|
||||
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet the encoding alphabet
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
boolean doPadding) {
|
||||
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
|
||||
int outLen = outBuff.length;
|
||||
|
||||
// If doPadding is false, set length to truncate '='
|
||||
// padding characters
|
||||
while (!doPadding && outLen > 0) {
|
||||
if (outBuff[outLen - 1] != '=') {
|
||||
break;
|
||||
}
|
||||
outLen -= 1;
|
||||
}
|
||||
|
||||
return new String(outBuff, 0, outLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @param maxLineLength maximum length of one line.
|
||||
* @return the BASE64-encoded byte array
|
||||
*/
|
||||
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
int maxLineLength) {
|
||||
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
|
||||
int len43 = lenDiv3 * 4;
|
||||
byte[] outBuff = new byte[len43 // Main 4:3
|
||||
+ (len43 / maxLineLength)]; // New lines
|
||||
|
||||
int d = 0;
|
||||
int e = 0;
|
||||
int len2 = len - 2;
|
||||
int lineLength = 0;
|
||||
for (; d < len2; d += 3, e += 4) {
|
||||
|
||||
// The following block of code is the same as
|
||||
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
|
||||
// but inlined for faster encoding (~20% improvement)
|
||||
int inBuff =
|
||||
((source[d + off] << 24) >>> 8)
|
||||
| ((source[d + 1 + off] << 24) >>> 16)
|
||||
| ((source[d + 2 + off] << 24) >>> 24);
|
||||
outBuff[e] = alphabet[(inBuff >>> 18)];
|
||||
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
lineLength = 0;
|
||||
} // end if: end of line
|
||||
} // end for: each piece of array
|
||||
|
||||
if (d < len) {
|
||||
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
// Add a last newline
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
}
|
||||
e += 4;
|
||||
}
|
||||
|
||||
assert (e == outBuff.length);
|
||||
return outBuff;
|
||||
}
|
||||
|
||||
|
||||
/* ******** D E C O D I N G M E T H O D S ******** */
|
||||
|
||||
|
||||
/**
|
||||
* Decodes four bytes from array <var>source</var>
|
||||
* and writes the resulting bytes (up to three of them)
|
||||
* to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 4 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 3 for
|
||||
* the <var>destination</var> array.
|
||||
* This method returns the actual number of bytes that
|
||||
* were converted from the Base64 encoding.
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return the number of decoded bytes converted
|
||||
* @since 1.3
|
||||
*/
|
||||
private static int decode4to3(byte[] source, int srcOffset,
|
||||
byte[] destination, int destOffset, byte[] decodabet) {
|
||||
// Example: Dk==
|
||||
if (source[srcOffset + 2] == EQUALS_SIGN) {
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
return 1;
|
||||
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >>> 8);
|
||||
return 2;
|
||||
} else {
|
||||
// Example: DkLE
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
|
||||
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >> 8);
|
||||
destination[destOffset + 2] = (byte) (outBuff);
|
||||
return 3;
|
||||
}
|
||||
} // end decodeToBytes
|
||||
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation.
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decode(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from web safe Base64 notation.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decodeWebSafe(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source The Base64 encoded data
|
||||
* @return decoded data
|
||||
* @throws Base64DecoderException
|
||||
* @since 1.3
|
||||
*/
|
||||
public static byte[] decode(byte[] source) throws Base64DecoderException {
|
||||
return decode(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded data.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source)
|
||||
throws Base64DecoderException {
|
||||
return decodeWebSafe(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
* @throws Base64DecoderException
|
||||
* @since 1.3
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, WEBSAFE_DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content using the supplied decodabet and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
|
||||
throws Base64DecoderException {
|
||||
int len34 = len * 3 / 4;
|
||||
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
|
||||
int outBuffPosn = 0;
|
||||
|
||||
byte[] b4 = new byte[4];
|
||||
int b4Posn = 0;
|
||||
int i = 0;
|
||||
byte sbiCrop = 0;
|
||||
byte sbiDecode = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
|
||||
sbiDecode = decodabet[sbiCrop];
|
||||
|
||||
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
|
||||
if (sbiDecode >= EQUALS_SIGN_ENC) {
|
||||
// An equals sign (for padding) must not occur at position 0 or 1
|
||||
// and must be the last byte[s] in the encoded value
|
||||
if (sbiCrop == EQUALS_SIGN) {
|
||||
int bytesLeft = len - i;
|
||||
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
|
||||
if (b4Posn == 0 || b4Posn == 1) {
|
||||
throw new Base64DecoderException(
|
||||
"invalid padding byte '=' at byte offset " + i);
|
||||
} else if ((b4Posn == 3 && bytesLeft > 2)
|
||||
|| (b4Posn == 4 && bytesLeft > 1)) {
|
||||
throw new Base64DecoderException(
|
||||
"padding byte '=' falsely signals end of encoded value "
|
||||
+ "at offset " + i);
|
||||
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
|
||||
throw new Base64DecoderException(
|
||||
"encoded value has invalid trailing byte");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
b4[b4Posn++] = sbiCrop;
|
||||
if (b4Posn == 4) {
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
b4Posn = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Base64DecoderException("Bad Base64 input character at " + i
|
||||
+ ": " + source[i + off] + "(decimal)");
|
||||
}
|
||||
}
|
||||
|
||||
// Because web safe encoding allows non padding base64 encodes, we
|
||||
// need to pad the rest of the b4 buffer with equal signs when
|
||||
// b4Posn != 0. There can be at most 2 equal signs at the end of
|
||||
// four characters, so the b4 buffer must have two or three
|
||||
// characters. This also catches the case where the input is
|
||||
// padded with EQUALS_SIGN
|
||||
if (b4Posn != 0) {
|
||||
if (b4Posn == 1) {
|
||||
throw new Base64DecoderException("single trailing character at offset "
|
||||
+ (len - 1));
|
||||
}
|
||||
b4[b4Posn++] = EQUALS_SIGN;
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
}
|
||||
|
||||
byte[] out = new byte[outBuffPosn];
|
||||
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Exception thrown when encountering an invalid Base64 input character.
|
||||
*
|
||||
* @author nelson
|
||||
*/
|
||||
public class Base64DecoderException extends Exception {
|
||||
public Base64DecoderException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Base64DecoderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Exception thrown when something went wrong with in-app billing.
|
||||
* An IabException has an associated IabResult (an error).
|
||||
* To get the IAB result that caused this exception to be thrown,
|
||||
* call {@link #getResult()}.
|
||||
*/
|
||||
public class IabException extends Exception {
|
||||
IabResult mResult;
|
||||
|
||||
public IabException(IabResult r) {
|
||||
this(r, null);
|
||||
}
|
||||
public IabException(int response, String message) {
|
||||
this(new IabResult(response, message));
|
||||
}
|
||||
public IabException(IabResult r, Exception cause) {
|
||||
super(r.getMessage(), cause);
|
||||
mResult = r;
|
||||
}
|
||||
public IabException(int response, String message, Exception cause) {
|
||||
this(new IabResult(response, message), cause);
|
||||
}
|
||||
|
||||
/** Returns the IAB result (error) that this exception signals. */
|
||||
public IabResult getResult() { return mResult; }
|
||||
}
|
|
@ -1,981 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.vending.billing.IInAppBillingService;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Provides convenience methods for in-app billing. You can create one instance of this
|
||||
* class for your application and use it to process in-app billing operations.
|
||||
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
|
||||
* many common in-app billing operations, as well as automatic signature
|
||||
* verification.
|
||||
* <p>
|
||||
* After instantiating, you must perform setup in order to start using the object.
|
||||
* To perform setup, call the {@link #startSetup} method and provide a listener;
|
||||
* that listener will be notified when setup is complete, after which (and not before)
|
||||
* you may call other methods.
|
||||
* <p>
|
||||
* After setup is complete, you will typically want to request an inventory of owned
|
||||
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
|
||||
* and related methods.
|
||||
* <p>
|
||||
* When you are done with this object, don't forget to call {@link #dispose}
|
||||
* to ensure proper cleanup. This object holds a binding to the in-app billing
|
||||
* service, which will leak unless you dispose of it correctly. If you created
|
||||
* the object on an Activity's onCreate method, then the recommended
|
||||
* place to dispose of it is the Activity's onDestroy method.
|
||||
* <p>
|
||||
* A note about threading: When using this object from a background thread, you may
|
||||
* call the blocking versions of methods; when using from a UI thread, call
|
||||
* only the asynchronous versions and handle the results via callbacks.
|
||||
* Also, notice that you can only call one asynchronous operation at a time;
|
||||
* attempting to start a second asynchronous operation while the first one
|
||||
* has not yet completed will result in an exception being thrown.
|
||||
*
|
||||
* @author Bruno Oliveira (Google)
|
||||
*/
|
||||
public class IabHelper {
|
||||
// Billing response codes
|
||||
public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
||||
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
|
||||
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
|
||||
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
|
||||
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
|
||||
// IAB Helper error codes
|
||||
public static final int IABHELPER_ERROR_BASE = -1000;
|
||||
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
|
||||
public static final int IABHELPER_BAD_RESPONSE = -1002;
|
||||
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
|
||||
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
|
||||
public static final int IABHELPER_USER_CANCELLED = -1005;
|
||||
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
|
||||
public static final int IABHELPER_MISSING_TOKEN = -1007;
|
||||
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
|
||||
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
|
||||
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
|
||||
// Keys for the responses from InAppBillingService
|
||||
public static final String RESPONSE_CODE = "RESPONSE_CODE";
|
||||
public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
|
||||
public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
|
||||
public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
|
||||
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
|
||||
public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
|
||||
public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
|
||||
public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
|
||||
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
|
||||
// Item types
|
||||
public static final String ITEM_TYPE_INAPP = "inapp";
|
||||
public static final String ITEM_TYPE_SUBS = "subs";
|
||||
// some fields on the getSkuDetails response bundle
|
||||
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
|
||||
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
|
||||
// Is debug logging enabled?
|
||||
boolean mDebugLog = false;
|
||||
String mDebugTag = "IabHelper";
|
||||
// Is setup done?
|
||||
boolean mSetupDone = false;
|
||||
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
|
||||
boolean mDisposed = false;
|
||||
// Are subscriptions supported?
|
||||
boolean mSubscriptionsSupported = false;
|
||||
// Is an asynchronous operation in progress?
|
||||
// (only one at a time can be in progress)
|
||||
boolean mAsyncInProgress = false;
|
||||
// (for logging/debugging)
|
||||
// if mAsyncInProgress == true, what asynchronous operation is in progress?
|
||||
String mAsyncOperation = "";
|
||||
// Context we were passed during initialization
|
||||
Context mContext;
|
||||
// Connection to the service
|
||||
IInAppBillingService mService;
|
||||
ServiceConnection mServiceConn;
|
||||
boolean mServiceBound;
|
||||
// The request code used to launch purchase flow
|
||||
int mRequestCode;
|
||||
// The item type of the current purchase flow
|
||||
String mPurchasingItemType;
|
||||
// Public key for verifying signature, in base64 encoding
|
||||
String mSignatureBase64 = null;
|
||||
// The listener registered on launchPurchaseFlow, which we have to call back when
|
||||
// the purchase finishes
|
||||
OnIabPurchaseFinishedListener mPurchaseListener;
|
||||
|
||||
/**
|
||||
* Creates an instance. After creation, it will not yet be ready to use. You must perform
|
||||
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
|
||||
* block and is safe to call from a UI thread.
|
||||
*
|
||||
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
|
||||
* @param base64PublicKey Your application's public key, encoded in base64.
|
||||
* This is used for verification of purchase signatures. You can find your app's base64-encoded
|
||||
* public key in your application's page on Google Play Developer Console. Note that this
|
||||
* is NOT your "developer public key".
|
||||
*/
|
||||
public IabHelper(Context ctx, String base64PublicKey) {
|
||||
mContext = ctx.getApplicationContext();
|
||||
mSignatureBase64 = base64PublicKey;
|
||||
logDebug("IAB helper created.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description for the given response code.
|
||||
*
|
||||
* @param code The response code
|
||||
* @return A human-readable string explaining the result code.
|
||||
* It also includes the result code numerically.
|
||||
*/
|
||||
public static String getResponseDesc(int code) {
|
||||
String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
|
||||
"3:Billing Unavailable/4:Item unavailable/" +
|
||||
"5:Developer Error/6:Error/7:Item Already Owned/" +
|
||||
"8:Item not owned").split("/");
|
||||
String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
|
||||
"-1002:Bad response received/" +
|
||||
"-1003:Purchase signature verification failed/" +
|
||||
"-1004:Send intent failed/" +
|
||||
"-1005:User cancelled/" +
|
||||
"-1006:Unknown purchase response/" +
|
||||
"-1007:Missing token/" +
|
||||
"-1008:Unknown error/" +
|
||||
"-1009:Subscriptions not available/" +
|
||||
"-1010:Invalid consumption attempt").split("/");
|
||||
|
||||
if (code <= IABHELPER_ERROR_BASE) {
|
||||
int index = IABHELPER_ERROR_BASE - code;
|
||||
if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
|
||||
else return code + ":Unknown IAB Helper Error";
|
||||
} else if (code < 0 || code >= iab_msgs.length)
|
||||
return code + ":Unknown";
|
||||
else
|
||||
return iab_msgs[code];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable debug logging through LogCat.
|
||||
*/
|
||||
public void enableDebugLogging(boolean enable, String tag) {
|
||||
checkNotDisposed();
|
||||
mDebugLog = enable;
|
||||
mDebugTag = tag;
|
||||
}
|
||||
|
||||
public void enableDebugLogging(boolean enable) {
|
||||
checkNotDisposed();
|
||||
mDebugLog = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the setup process. This will start up the setup process asynchronously.
|
||||
* You will be notified through the listener when the setup process is complete.
|
||||
* This method is safe to call from a UI thread.
|
||||
*
|
||||
* @param listener The listener to notify when the setup process is complete.
|
||||
*/
|
||||
public void startSetup(final OnIabSetupFinishedListener listener) {
|
||||
// If already set up, can't do it again.
|
||||
checkNotDisposed();
|
||||
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
|
||||
|
||||
// Connection to IAB service
|
||||
logDebug("Starting in-app billing setup.");
|
||||
mServiceConn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
logDebug("Billing service disconnected.");
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (mDisposed) return;
|
||||
logDebug("Billing service connected.");
|
||||
mService = IInAppBillingService.Stub.asInterface(service);
|
||||
String packageName = mContext.getPackageName();
|
||||
try {
|
||||
logDebug("Checking for in-app billing 3 support.");
|
||||
|
||||
if (mService == null) {
|
||||
if (listener != null)
|
||||
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
|
||||
"Error checking for billing v3 support, no service."));
|
||||
|
||||
// if in-app purchases aren't supported, neither are subscriptions.
|
||||
mSubscriptionsSupported = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// check for in-app billing v3 support
|
||||
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
|
||||
"Error checking for billing v3 support."));
|
||||
|
||||
// if in-app purchases aren't supported, neither are subscriptions.
|
||||
mSubscriptionsSupported = false;
|
||||
return;
|
||||
}
|
||||
logDebug("In-app billing version 3 supported for " + packageName);
|
||||
|
||||
// check for v3 subscriptions support
|
||||
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
|
||||
if (response == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Subscriptions AVAILABLE.");
|
||||
mSubscriptionsSupported = true;
|
||||
} else {
|
||||
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
|
||||
}
|
||||
|
||||
mSetupDone = true;
|
||||
} catch (RemoteException e) {
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
|
||||
"RemoteException while setting up in-app billing."));
|
||||
}
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
||||
serviceIntent.setPackage("com.android.vending");
|
||||
final List<ResolveInfo> intentServices = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
|
||||
if (intentServices != null && !intentServices.isEmpty()) {
|
||||
// service available to handle that Intent
|
||||
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
|
||||
mServiceBound = true;
|
||||
} else {
|
||||
// no service available to handle that Intent
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(
|
||||
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
|
||||
"Billing service unavailable on device."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSetupDone() {
|
||||
return mSetupDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of object, releasing resources. It's very important to call this
|
||||
* method when you are done with this object. It will release any resources
|
||||
* used by it such as service connections. Naturally, once the object is
|
||||
* disposed of, it can't be used again.
|
||||
*/
|
||||
public void dispose() {
|
||||
logDebug("Disposing.");
|
||||
mSetupDone = false;
|
||||
if (mServiceConn != null) {
|
||||
logDebug("Unbinding from service.");
|
||||
if (mContext != null && mServiceBound) {
|
||||
mContext.unbindService(mServiceConn);
|
||||
mServiceBound = false;
|
||||
}
|
||||
}
|
||||
mDisposed = true;
|
||||
mContext = null;
|
||||
mServiceConn = null;
|
||||
mService = null;
|
||||
mPurchaseListener = null;
|
||||
}
|
||||
|
||||
private void checkNotDisposed() {
|
||||
if (mDisposed)
|
||||
throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether subscriptions are supported.
|
||||
*/
|
||||
public boolean subscriptionsSupported() {
|
||||
checkNotDisposed();
|
||||
return mSubscriptionsSupported;
|
||||
}
|
||||
|
||||
public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
|
||||
launchPurchaseFlow(act, sku, requestCode, listener, "");
|
||||
}
|
||||
|
||||
public void launchPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
|
||||
}
|
||||
|
||||
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener) {
|
||||
launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
|
||||
}
|
||||
|
||||
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
|
||||
* which will involve bringing up the Google Play screen. The calling activity will be paused while
|
||||
* the user interacts with Google Play, and the result will be delivered via the activity's
|
||||
* {@link Activity#onActivityResult} method, at which point you must call
|
||||
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
|
||||
* MUST be called from the UI thread of the Activity.
|
||||
*
|
||||
* @param act The calling activity.
|
||||
* @param sku The sku of the item to purchase.
|
||||
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
|
||||
* @param requestCode A request code (to differentiate from other responses --
|
||||
* as in {@link Activity#startActivityForResult}).
|
||||
* @param listener The listener to notify when the purchase process finishes
|
||||
* @param extraData Extra data (developer payload), which will be returned with the purchase data
|
||||
* when the purchase completes. This extra data will be permanently bound to that purchase
|
||||
* and will always be returned when the purchase is queried.
|
||||
*/
|
||||
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("launchPurchaseFlow");
|
||||
flagStartAsync("launchPurchaseFlow");
|
||||
IabResult result;
|
||||
|
||||
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
|
||||
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
|
||||
"Subscriptions are not available.");
|
||||
flagEndAsync();
|
||||
if (listener != null) listener.onIabPurchaseFinished(r, null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
|
||||
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
|
||||
int response = getResponseCodeFromBundle(buyIntentBundle);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logError("Unable to buy item, Error response: " + getResponseDesc(response));
|
||||
flagEndAsync();
|
||||
result = new IabResult(response, "Unable to buy item");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
|
||||
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
|
||||
mRequestCode = requestCode;
|
||||
mPurchaseListener = listener;
|
||||
mPurchasingItemType = itemType;
|
||||
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
|
||||
requestCode, new Intent(),
|
||||
Integer.valueOf(0), Integer.valueOf(0),
|
||||
Integer.valueOf(0));
|
||||
} catch (SendIntentException e) {
|
||||
logError("SendIntentException while launching purchase flow for sku " + sku);
|
||||
e.printStackTrace();
|
||||
flagEndAsync();
|
||||
|
||||
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
} catch (RemoteException e) {
|
||||
logError("RemoteException while launching purchase flow for sku " + sku);
|
||||
e.printStackTrace();
|
||||
flagEndAsync();
|
||||
|
||||
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an activity result that's part of the purchase flow in in-app billing. If you
|
||||
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
|
||||
* Activity's {@link Activity@onActivityResult} method. This method
|
||||
* MUST be called from the UI thread of the Activity.
|
||||
*
|
||||
* @param requestCode The requestCode as you received it.
|
||||
* @param resultCode The resultCode as you received it.
|
||||
* @param data The data (Intent) as you received it.
|
||||
* @return Returns true if the result was related to a purchase flow and was handled;
|
||||
* false if the result was not related to a purchase, in which case you should
|
||||
* handle it normally.
|
||||
*/
|
||||
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
IabResult result;
|
||||
if (requestCode != mRequestCode) return false;
|
||||
|
||||
checkNotDisposed();
|
||||
checkSetupDone("handleActivityResult");
|
||||
|
||||
// end of async purchase operation that started on launchPurchaseFlow
|
||||
flagEndAsync();
|
||||
|
||||
if (data == null) {
|
||||
logError("Null data in IAB activity result.");
|
||||
result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
int responseCode = getResponseCodeFromIntent(data);
|
||||
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
|
||||
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
|
||||
|
||||
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Successful resultcode from purchase activity.");
|
||||
logDebug("Purchase data: " + purchaseData);
|
||||
logDebug("Data signature: " + dataSignature);
|
||||
logDebug("Extras: " + data.getExtras());
|
||||
logDebug("Expected item type: " + mPurchasingItemType);
|
||||
|
||||
if (purchaseData == null || dataSignature == null) {
|
||||
logError("BUG: either purchaseData or dataSignature is null.");
|
||||
logDebug("Extras: " + data.getExtras().toString());
|
||||
result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
Purchase purchase = null;
|
||||
try {
|
||||
purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
|
||||
String sku = purchase.getSku();
|
||||
|
||||
// Verify signature
|
||||
if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
|
||||
logError("Purchase signature verification FAILED for sku " + sku);
|
||||
result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, purchase);
|
||||
return true;
|
||||
}
|
||||
logDebug("Purchase signature successfully verified.");
|
||||
} catch (JSONException e) {
|
||||
logError("Failed to parse purchase data.");
|
||||
e.printStackTrace();
|
||||
result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mPurchaseListener != null) {
|
||||
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_OK) {
|
||||
// result code was OK, but in-app billing response was not OK.
|
||||
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
|
||||
if (mPurchaseListener != null) {
|
||||
result = new IabResult(responseCode, "Problem purchashing item.");
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
|
||||
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
} else {
|
||||
logError("Purchase failed. Result code: " + resultCode
|
||||
+ ". Response: " + getResponseDesc(responseCode));
|
||||
result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
|
||||
return queryInventory(querySkuDetails, moreSkus, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the inventory. This will query all owned items from the server, as well as
|
||||
* information on additional skus, if specified. This method may block or take long to execute.
|
||||
* Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
|
||||
*
|
||||
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
|
||||
* as purchase information.
|
||||
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
|
||||
* Ignored if null or if querySkuDetails is false.
|
||||
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
|
||||
* Ignored if null or if querySkuDetails is false.
|
||||
* @throws IabException if a problem occurs while refreshing the inventory.
|
||||
*/
|
||||
public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
|
||||
List<String> moreSubsSkus) throws IabException {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("queryInventory");
|
||||
try {
|
||||
Inventory inv = new Inventory();
|
||||
int r = queryPurchases(inv, ITEM_TYPE_INAPP);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying owned items).");
|
||||
}
|
||||
|
||||
if (querySkuDetails) {
|
||||
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
|
||||
}
|
||||
}
|
||||
|
||||
// if subscriptions are supported, then also query for subscriptions
|
||||
if (mSubscriptionsSupported) {
|
||||
r = queryPurchases(inv, ITEM_TYPE_SUBS);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
|
||||
}
|
||||
|
||||
if (querySkuDetails) {
|
||||
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inv;
|
||||
} catch (RemoteException e) {
|
||||
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
|
||||
} catch (JSONException e) {
|
||||
throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper for inventory query. This will perform an inventory
|
||||
* query as described in {@link #queryInventory}, but will do so asynchronously
|
||||
* and call back the specified listener upon completion. This method is safe to
|
||||
* call from a UI thread.
|
||||
*
|
||||
* @param querySkuDetails as in {@link #queryInventory}
|
||||
* @param moreSkus as in {@link #queryInventory}
|
||||
* @param listener The listener to notify when the refresh operation completes.
|
||||
*/
|
||||
public void queryInventoryAsync(final boolean querySkuDetails,
|
||||
final List<String> moreSkus,
|
||||
final QueryInventoryFinishedListener listener) {
|
||||
final Handler handler = new Handler();
|
||||
checkNotDisposed();
|
||||
checkSetupDone("queryInventory");
|
||||
try {
|
||||
flagStartAsync("refresh inventory");
|
||||
} catch (IllegalStateException e) {
|
||||
return;
|
||||
}
|
||||
(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
|
||||
Inventory inv = null;
|
||||
try {
|
||||
inv = queryInventory(querySkuDetails, moreSkus);
|
||||
} catch (IabException ex) {
|
||||
result = ex.getResult();
|
||||
}
|
||||
|
||||
flagEndAsync();
|
||||
|
||||
final IabResult result_f = result;
|
||||
final Inventory inv_f = inv;
|
||||
if (!mDisposed && listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
listener.onQueryInventoryFinished(result_f, inv_f);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
|
||||
queryInventoryAsync(true, null, listener);
|
||||
}
|
||||
|
||||
public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
|
||||
queryInventoryAsync(querySkuDetails, null, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a given in-app product. Consuming can only be done on an item
|
||||
* that's owned, and as a result of consumption, the user will no longer own it.
|
||||
* This method may block or take long to return. Do not call from the UI thread.
|
||||
* For that, see {@link #consumeAsync}.
|
||||
*
|
||||
* @param itemInfo The PurchaseInfo that represents the item to consume.
|
||||
* @throws IabException if there is a problem during consumption.
|
||||
*/
|
||||
void consume(Purchase itemInfo) throws IabException {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
|
||||
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
|
||||
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
|
||||
"Items of type '" + itemInfo.mItemType + "' can't be consumed.");
|
||||
}
|
||||
|
||||
try {
|
||||
String token = itemInfo.getToken();
|
||||
String sku = itemInfo.getSku();
|
||||
if (token == null || token.equals("")) {
|
||||
logError("Can't consume " + sku + ". No token.");
|
||||
throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
|
||||
+ sku + " " + itemInfo);
|
||||
}
|
||||
|
||||
logDebug("Consuming sku: " + sku + ", token: " + token);
|
||||
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
|
||||
if (response == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Successfully consumed sku: " + sku);
|
||||
} else {
|
||||
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
|
||||
throw new IabException(response, "Error consuming sku " + sku);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
|
||||
* performs the consumption in the background and notifies completion through
|
||||
* the provided listener. This method is safe to call from a UI thread.
|
||||
*
|
||||
* @param purchase The purchase to be consumed.
|
||||
* @param listener The listener to notify when the consumption operation finishes.
|
||||
*/
|
||||
public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
List<Purchase> purchases = new ArrayList<Purchase>();
|
||||
purchases.add(purchase);
|
||||
consumeAsyncInternal(purchases, listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link consumeAsync}, but for multiple items at once.
|
||||
*
|
||||
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
|
||||
* @param listener The listener to notify when the consumption operation finishes.
|
||||
*/
|
||||
public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
consumeAsyncInternal(purchases, null, listener);
|
||||
}
|
||||
|
||||
// Checks that setup was done; if not, throws an exception.
|
||||
void checkSetupDone(String operation) {
|
||||
if (!mSetupDone) {
|
||||
logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
|
||||
throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
||||
int getResponseCodeFromBundle(Bundle b) {
|
||||
Object o = b.get(RESPONSE_CODE);
|
||||
if (o == null) {
|
||||
logDebug("Bundle with null response code, assuming OK (known issue)");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
} else if (o instanceof Integer) return ((Integer) o).intValue();
|
||||
else if (o instanceof Long) return (int) ((Long) o).longValue();
|
||||
else {
|
||||
logError("Unexpected type for bundle response code.");
|
||||
logError(o.getClass().getName());
|
||||
throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
||||
int getResponseCodeFromIntent(Intent i) {
|
||||
Object o = i.getExtras().get(RESPONSE_CODE);
|
||||
if (o == null) {
|
||||
logError("Intent with no response code, assuming OK (known issue)");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
} else if (o instanceof Integer) return ((Integer) o).intValue();
|
||||
else if (o instanceof Long) return (int) ((Long) o).longValue();
|
||||
else {
|
||||
logError("Unexpected type for intent response code.");
|
||||
logError(o.getClass().getName());
|
||||
throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
void flagStartAsync(String operation) {
|
||||
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
|
||||
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
|
||||
mAsyncOperation = operation;
|
||||
mAsyncInProgress = true;
|
||||
logDebug("Starting async operation: " + operation);
|
||||
}
|
||||
|
||||
void flagEndAsync() {
|
||||
logDebug("Ending async operation: " + mAsyncOperation);
|
||||
mAsyncOperation = "";
|
||||
mAsyncInProgress = false;
|
||||
}
|
||||
|
||||
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
|
||||
// Query purchases
|
||||
logDebug("Querying owned items, item type: " + itemType);
|
||||
logDebug("Package name: " + mContext.getPackageName());
|
||||
boolean verificationFailed = false;
|
||||
String continueToken = null;
|
||||
|
||||
do {
|
||||
logDebug("Calling getPurchases with continuation token: " + continueToken);
|
||||
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
|
||||
itemType, continueToken);
|
||||
|
||||
int response = getResponseCodeFromBundle(ownedItems);
|
||||
logDebug("Owned items response: " + response);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("getPurchases() failed: " + getResponseDesc(response));
|
||||
return response;
|
||||
}
|
||||
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|
||||
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|
||||
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
|
||||
logError("Bundle returned from getPurchases() doesn't contain required fields.");
|
||||
return IABHELPER_BAD_RESPONSE;
|
||||
}
|
||||
|
||||
ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_ITEM_LIST);
|
||||
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_PURCHASE_DATA_LIST);
|
||||
ArrayList<String> signatureList = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_SIGNATURE_LIST);
|
||||
|
||||
for (int i = 0; i < purchaseDataList.size(); ++i) {
|
||||
String purchaseData = purchaseDataList.get(i);
|
||||
String signature = signatureList.get(i);
|
||||
String sku = ownedSkus.get(i);
|
||||
if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
|
||||
logDebug("Sku is owned: " + sku);
|
||||
Purchase purchase = new Purchase(itemType, purchaseData, signature);
|
||||
|
||||
if (TextUtils.isEmpty(purchase.getToken())) {
|
||||
logWarn("BUG: empty/null token!");
|
||||
logDebug("Purchase data: " + purchaseData);
|
||||
}
|
||||
|
||||
// Record ownership and token
|
||||
inv.addPurchase(purchase);
|
||||
} else {
|
||||
logWarn("Purchase signature verification **FAILED**. Not adding item.");
|
||||
logDebug(" Purchase data: " + purchaseData);
|
||||
logDebug(" Signature: " + signature);
|
||||
verificationFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
|
||||
logDebug("Continuation token: " + continueToken);
|
||||
} while (!TextUtils.isEmpty(continueToken));
|
||||
|
||||
return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
|
||||
throws RemoteException, JSONException {
|
||||
logDebug("Querying SKU details.");
|
||||
ArrayList<String> skuList = new ArrayList<String>();
|
||||
skuList.addAll(inv.getAllOwnedSkus(itemType));
|
||||
if (moreSkus != null) {
|
||||
for (String sku : moreSkus) {
|
||||
if (!skuList.contains(sku)) {
|
||||
skuList.add(sku);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skuList.size() == 0) {
|
||||
logDebug("queryPrices: nothing to do because there are no SKUs.");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
Bundle querySkus = new Bundle();
|
||||
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
|
||||
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
|
||||
itemType, querySkus);
|
||||
|
||||
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
|
||||
int response = getResponseCodeFromBundle(skuDetails);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
|
||||
return response;
|
||||
} else {
|
||||
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
|
||||
return IABHELPER_BAD_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> responseList = skuDetails.getStringArrayList(
|
||||
RESPONSE_GET_SKU_DETAILS_LIST);
|
||||
|
||||
for (String thisResponse : responseList) {
|
||||
SkuDetails d = new SkuDetails(itemType, thisResponse);
|
||||
logDebug("Got sku details: " + d);
|
||||
inv.addSkuDetails(d);
|
||||
}
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
void consumeAsyncInternal(final List<Purchase> purchases,
|
||||
final OnConsumeFinishedListener singleListener,
|
||||
final OnConsumeMultiFinishedListener multiListener) {
|
||||
final Handler handler = new Handler();
|
||||
flagStartAsync("consume");
|
||||
(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
final List<IabResult> results = new ArrayList<IabResult>();
|
||||
for (Purchase purchase : purchases) {
|
||||
try {
|
||||
consume(purchase);
|
||||
results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
|
||||
} catch (IabException ex) {
|
||||
results.add(ex.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
flagEndAsync();
|
||||
if (!mDisposed && singleListener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!mDisposed && multiListener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
multiListener.onConsumeMultiFinished(purchases, results);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
void logDebug(String msg) {
|
||||
if (mDebugLog) Log.d(mDebugTag, msg);
|
||||
}
|
||||
|
||||
void logError(String msg) {
|
||||
Log.e(mDebugTag, "In-app billing error: " + msg);
|
||||
}
|
||||
|
||||
void logWarn(String msg) {
|
||||
Log.w(mDebugTag, "In-app billing warning: " + msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
|
||||
* when the setup process is complete.
|
||||
*/
|
||||
public interface OnIabSetupFinishedListener {
|
||||
/**
|
||||
* Called to notify that setup is complete.
|
||||
*
|
||||
* @param result The result of the setup process.
|
||||
*/
|
||||
void onIabSetupFinished(IabResult result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback that notifies when a purchase is finished.
|
||||
*/
|
||||
public interface OnIabPurchaseFinishedListener {
|
||||
/**
|
||||
* Called to notify that an in-app purchase finished. If the purchase was successful,
|
||||
* then the sku parameter specifies which item was purchased. If the purchase failed,
|
||||
* the sku and extraData parameters may or may not be null, depending on how far the purchase
|
||||
* process went.
|
||||
*
|
||||
* @param result The result of the purchase.
|
||||
* @param info The purchase information (null if purchase failed)
|
||||
*/
|
||||
void onIabPurchaseFinished(IabResult result, Purchase info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that notifies when an inventory query operation completes.
|
||||
*/
|
||||
public interface QueryInventoryFinishedListener {
|
||||
/**
|
||||
* Called to notify that an inventory query operation completed.
|
||||
*
|
||||
* @param result The result of the operation.
|
||||
* @param inv The inventory.
|
||||
*/
|
||||
void onQueryInventoryFinished(IabResult result, Inventory inv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that notifies when a consumption operation finishes.
|
||||
*/
|
||||
public interface OnConsumeFinishedListener {
|
||||
/**
|
||||
* Called to notify that a consumption has finished.
|
||||
*
|
||||
* @param purchase The purchase that was (or was to be) consumed.
|
||||
* @param result The result of the consumption operation.
|
||||
*/
|
||||
void onConsumeFinished(Purchase purchase, IabResult result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that notifies when a multi-item consumption operation finishes.
|
||||
*/
|
||||
public interface OnConsumeMultiFinishedListener {
|
||||
/**
|
||||
* Called to notify that a consumption of multiple items has finished.
|
||||
*
|
||||
* @param purchases The purchases that were (or were to be) consumed.
|
||||
* @param results The results of each consumption operation, corresponding to each
|
||||
* sku.
|
||||
*/
|
||||
void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Represents the result of an in-app billing operation.
|
||||
* A result is composed of a response code (an integer) and possibly a
|
||||
* message (String). You can get those by calling
|
||||
* {@link #getResponse} and {@link #getMessage()}, respectively. You
|
||||
* can also inquire whether a result is a success or a failure by
|
||||
* calling {@link #isSuccess()} and {@link #isFailure()}.
|
||||
*/
|
||||
public class IabResult {
|
||||
int mResponse;
|
||||
String mMessage;
|
||||
|
||||
public IabResult(int response, String message) {
|
||||
mResponse = response;
|
||||
if (message == null || message.trim().length() == 0) {
|
||||
mMessage = IabHelper.getResponseDesc(response);
|
||||
}
|
||||
else {
|
||||
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
|
||||
}
|
||||
}
|
||||
public int getResponse() { return mResponse; }
|
||||
public String getMessage() { return mMessage; }
|
||||
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
|
||||
public boolean isFailure() { return !isSuccess(); }
|
||||
public String toString() { return "IabResult: " + getMessage(); }
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a block of information about in-app items.
|
||||
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
|
||||
*/
|
||||
public class Inventory {
|
||||
Map<String, SkuDetails> mSkuMap = new HashMap<String, SkuDetails>();
|
||||
Map<String, Purchase> mPurchaseMap = new HashMap<String, Purchase>();
|
||||
|
||||
Inventory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the listing details for an in-app product.
|
||||
*/
|
||||
public SkuDetails getSkuDetails(String sku) {
|
||||
return mSkuMap.get(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns purchase information for a given product, or null if there is no purchase.
|
||||
*/
|
||||
public Purchase getPurchase(String sku) {
|
||||
return mPurchaseMap.get(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not there exists a purchase of the given product.
|
||||
*/
|
||||
public boolean hasPurchase(String sku) {
|
||||
return mPurchaseMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not details about the given product are available.
|
||||
*/
|
||||
public boolean hasDetails(String sku) {
|
||||
return mSkuMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase a purchase (locally) from the inventory, given its product ID. This just
|
||||
* modifies the Inventory object locally and has no effect on the server! This is
|
||||
* useful when you have an existing Inventory object which you know to be up to date,
|
||||
* and you have just consumed an item successfully, which means that erasing its
|
||||
* purchase data from the Inventory you already have is quicker than querying for
|
||||
* a new Inventory.
|
||||
*/
|
||||
public void erasePurchase(String sku) {
|
||||
mPurchaseMap.remove(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all owned product IDs.
|
||||
*/
|
||||
List<String> getAllOwnedSkus() {
|
||||
return new ArrayList<String>(mPurchaseMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all owned product IDs of a given type
|
||||
*/
|
||||
List<String> getAllOwnedSkus(String itemType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Purchase p : mPurchaseMap.values()) {
|
||||
if (p.getItemType().equals(itemType)) result.add(p.getSku());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all purchases.
|
||||
*/
|
||||
List<Purchase> getAllPurchases() {
|
||||
return new ArrayList<Purchase>(mPurchaseMap.values());
|
||||
}
|
||||
|
||||
void addSkuDetails(SkuDetails d) {
|
||||
mSkuMap.put(d.getSku(), d);
|
||||
}
|
||||
|
||||
void addPurchase(Purchase p) {
|
||||
mPurchaseMap.put(p.getSku(), p);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app billing purchase.
|
||||
*/
|
||||
public class Purchase {
|
||||
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
|
||||
String mOrderId;
|
||||
String mPackageName;
|
||||
String mSku;
|
||||
long mPurchaseTime;
|
||||
int mPurchaseState;
|
||||
String mDeveloperPayload;
|
||||
String mToken;
|
||||
String mOriginalJson;
|
||||
String mSignature;
|
||||
|
||||
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mOriginalJson = jsonPurchaseInfo;
|
||||
JSONObject o = new JSONObject(mOriginalJson);
|
||||
mOrderId = o.optString("orderId");
|
||||
mPackageName = o.optString("packageName");
|
||||
mSku = o.optString("productId");
|
||||
mPurchaseTime = o.optLong("purchaseTime");
|
||||
mPurchaseState = o.optInt("purchaseState");
|
||||
mDeveloperPayload = o.optString("developerPayload");
|
||||
mToken = o.optString("token", o.optString("purchaseToken"));
|
||||
mSignature = signature;
|
||||
}
|
||||
|
||||
public String getItemType() { return mItemType; }
|
||||
public String getOrderId() { return mOrderId; }
|
||||
public String getPackageName() { return mPackageName; }
|
||||
public String getSku() { return mSku; }
|
||||
public long getPurchaseTime() { return mPurchaseTime; }
|
||||
public int getPurchaseState() { return mPurchaseState; }
|
||||
public String getDeveloperPayload() { return mDeveloperPayload; }
|
||||
public String getToken() { return mToken; }
|
||||
public String getOriginalJson() { return mOriginalJson; }
|
||||
public String getSignature() { return mSignature; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import net.pierrox.lightning_launcher.BuildConfig;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* Security-related methods. For a secure implementation, all of this code
|
||||
* should be implemented on a server that communicates with the
|
||||
* application on the device. For the sake of simplicity and clarity of this
|
||||
* example, this code is included here and is executed on the device. If you
|
||||
* must verify the purchases on the phone, you should obfuscate this code to
|
||||
* make it harder for an attacker to replace the code with stubs that treat all
|
||||
* purchases as verified.
|
||||
*/
|
||||
public class Security {
|
||||
private static final String TAG = "IABUtil/Security";
|
||||
|
||||
private static final String KEY_FACTORY_ALGORITHM = "RSA";
|
||||
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
|
||||
|
||||
/**
|
||||
* Verifies that the data was signed with the given signature, and returns
|
||||
* the verified purchase. The data is in JSON format and signed
|
||||
* with a private key. The data also contains the {@link PurchaseState}
|
||||
* and product ID of the purchase.
|
||||
* @param base64PublicKey the base64-encoded public key to use for verifying.
|
||||
* @param signedData the signed JSON string (signed, not encrypted)
|
||||
* @param signature the signature for the data, signed with the private key
|
||||
*/
|
||||
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return true;
|
||||
}
|
||||
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
|
||||
TextUtils.isEmpty(signature)) {
|
||||
Log.e(TAG, "Purchase verification failed: missing data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PublicKey key = Security.generatePublicKey(base64PublicKey);
|
||||
return Security.verify(key, signedData, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PublicKey instance from a string containing the
|
||||
* Base64-encoded public key.
|
||||
*
|
||||
* @param encodedPublicKey Base64-encoded public key
|
||||
* @throws IllegalArgumentException if encodedPublicKey is invalid
|
||||
*/
|
||||
public static PublicKey generatePublicKey(String encodedPublicKey) {
|
||||
try {
|
||||
byte[] decodedKey = Base64.decode(encodedPublicKey);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the signature from the server matches the computed
|
||||
* signature on the data. Returns true if the data is correctly signed.
|
||||
*
|
||||
* @param publicKey public key associated with the developer account
|
||||
* @param signedData signed data from server
|
||||
* @param signature server signature
|
||||
* @return true if the data and signature match
|
||||
*/
|
||||
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
|
||||
Signature sig;
|
||||
try {
|
||||
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(signedData.getBytes());
|
||||
if (!sig.verify(Base64.decode(signature))) {
|
||||
Log.e(TAG, "Signature verification failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "NoSuchAlgorithmException.");
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Signature exception.");
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app product's listing details.
|
||||
*/
|
||||
public class SkuDetails {
|
||||
String mItemType;
|
||||
String mSku;
|
||||
String mType;
|
||||
String mPrice;
|
||||
String mTitle;
|
||||
String mDescription;
|
||||
String mJson;
|
||||
|
||||
public SkuDetails(String jsonSkuDetails) throws JSONException {
|
||||
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
|
||||
}
|
||||
|
||||
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mJson = jsonSkuDetails;
|
||||
JSONObject o = new JSONObject(mJson);
|
||||
mSku = o.optString("productId");
|
||||
mType = o.optString("type");
|
||||
mPrice = o.optString("price");
|
||||
mTitle = o.optString("title");
|
||||
mDescription = o.optString("description");
|
||||
}
|
||||
|
||||
public String getSku() { return mSku; }
|
||||
public String getType() { return mType; }
|
||||
public String getPrice() { return mPrice; }
|
||||
public String getTitle() { return mTitle; }
|
||||
public String getDescription() { return mDescription; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SkuDetails:" + mJson;
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
|
||||
<application
|
||||
android:name="net.pierrox.lightning_launcher.${app_name}"
|
||||
android:name="net.pierrox.lightning_launcher.LLAppPhone"
|
||||
android:icon="@drawable/icon"
|
||||
android:theme="@style/AppLight"
|
||||
android:label="@string/app_name">
|
||||
|
|
|
@ -27,7 +27,6 @@ package net.pierrox.lightning_launcher;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.pierrox.lightning_launcher.activities.AppDrawerX;
|
||||
|
@ -38,12 +37,12 @@ import net.pierrox.lightning_launcher.api.ScreenIdentity;
|
|||
import net.pierrox.lightning_launcher.data.Page;
|
||||
import net.pierrox.lightning_launcher.engine.Screen;
|
||||
import net.pierrox.lightning_launcher.overlay.WindowService;
|
||||
import net.pierrox.lightning_launcher.prefs.LLPreference;
|
||||
import net.pierrox.lightning_launcher.script.Script;
|
||||
import net.pierrox.lightning_launcher.util.EmptyService;
|
||||
import net.pierrox.lightning_launcher.util.MPReceiver;
|
||||
|
||||
public abstract class LLAppPhone extends LLApp {
|
||||
|
||||
public class LLAppPhone extends LLApp {
|
||||
|
||||
private MPReceiver mMPReceiver;
|
||||
|
||||
|
@ -159,8 +158,4 @@ public abstract class LLAppPhone extends LLApp {
|
|||
mAppEngine.writeCurrentPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract View managePreferenceViewLockedFlag(LLPreference preference, View preference_view);
|
||||
|
||||
public abstract void manageAddItemDialogLockedFlag(View add_item_view, boolean locked);
|
||||
}
|
||||
|
|
|
@ -1044,11 +1044,6 @@ public class AppDrawerX extends Dashboard implements EditTextIme.OnEditTextImeLi
|
|||
|
||||
@Override
|
||||
protected void addFolder() {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemLayout il = mScreen.getTargetOrTopmostItemLayout();
|
||||
Page page = il.getPage();
|
||||
|
||||
|
@ -1338,12 +1333,7 @@ public class AppDrawerX extends Dashboard implements EditTextIme.OnEditTextImeLi
|
|||
break;
|
||||
|
||||
case R.id.mi_kill:
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
} else {
|
||||
// use super class behavior
|
||||
super.onClick(v);
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.mi_i:
|
||||
|
|
|
@ -115,7 +115,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
private ApplyMode mApplyMode = ApplyMode.MERGE;
|
||||
private boolean mLoadWallpaper;
|
||||
private int mLLVersionFrom;
|
||||
private boolean mWarningFreeVersion;
|
||||
private boolean mWarningScreen;
|
||||
private boolean mWarningWidget;
|
||||
private SparseArray<ComponentName> mAppWidgetsToBind;
|
||||
|
@ -171,7 +170,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
mFromStatusBarHeight = savedInstanceState.getInt("sbh");
|
||||
mTemplateDisplayName = savedInstanceState.getString("tn");
|
||||
mTemplateUri = savedInstanceState.getParcelable("tu");
|
||||
mWarningFreeVersion = savedInstanceState.getBoolean("wx");
|
||||
mWarningScreen = savedInstanceState.getBoolean("ws");
|
||||
mWarningWidget = savedInstanceState.getBoolean("ww");
|
||||
|
||||
|
@ -214,7 +212,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
outState.putInt("sbh", mFromStatusBarHeight);
|
||||
outState.putString("tn", mTemplateDisplayName);
|
||||
outState.putParcelable("tu", mTemplateUri);
|
||||
outState.putBoolean("wx", mWarningFreeVersion);
|
||||
outState.putBoolean("ws", mWarningScreen);
|
||||
outState.putBoolean("ww", mWarningWidget);
|
||||
|
||||
|
@ -301,10 +298,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.tmpl_warn_t);
|
||||
String msg = getString(R.string.tmpl_warn_m);
|
||||
|
||||
if (mWarningFreeVersion) {
|
||||
msg += "\n\n" + getString(R.string.tmpl_warn_llx);
|
||||
}
|
||||
if (mWarningScreen) {
|
||||
msg += "\n\n" + getString(R.string.tmpl_warn_screen);
|
||||
}
|
||||
|
@ -318,15 +311,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
applyTemplate();
|
||||
}
|
||||
});
|
||||
if (mWarningFreeVersion) {
|
||||
builder.setNeutralButton(R.string.tmpl_get_llx, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Utils.startAppStore(ApplyTemplate.this, getPackageName());
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
|
@ -381,8 +365,6 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
return;
|
||||
}
|
||||
|
||||
mWarningFreeVersion = LLApp.get().isFreeVersion();
|
||||
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
mFromScreenDpi = manifest.getInt(BackupRestoreTool.MANIFEST_SCREEN_DENSITY);
|
||||
mFromScreenWidth = manifest.getInt(BackupRestoreTool.MANIFEST_SCREEN_WIDTH);
|
||||
|
@ -393,11 +375,12 @@ public class ApplyTemplate extends ResourceWrapperActivity {
|
|||
|
||||
mWarningWidget = sBindAppWidgetIdIfAllowed == null;
|
||||
|
||||
if (mWarningFreeVersion || mWarningScreen || mWarningWidget) {
|
||||
if (mWarningScreen || mWarningWidget) {
|
||||
showDialog(DIALOG_WARNING);
|
||||
} else {
|
||||
applyTemplate();
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -128,15 +128,11 @@ public class BackupRestore extends ResourceWrapperActivity implements View.OnCli
|
|||
|
||||
loadArchivesList();
|
||||
|
||||
if (LLApp.get().isTrialVersion()) {
|
||||
Toast.makeText(this, R.string.tr_fl_t, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
checkPermissions(
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
new int[]{R.string.pr_r1, R.string.pr_r2},
|
||||
REQUEST_PERMISSION_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
|
@ -377,9 +373,6 @@ public class BackupRestore extends ResourceWrapperActivity implements View.OnCli
|
|||
}
|
||||
|
||||
private void loadArchive(Uri archiveUri, String archiveName) {
|
||||
if (LLApp.get().isTrialVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
} else {
|
||||
mArchiveUri = archiveUri;
|
||||
mArchiveName = archiveName;
|
||||
try {
|
||||
|
@ -388,7 +381,6 @@ public class BackupRestore extends ResourceWrapperActivity implements View.OnCli
|
|||
}
|
||||
showDialog(DIALOG_CONFIRM_RESTORE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
|
|
|
@ -31,8 +31,6 @@ import android.app.WallpaperManager;
|
|||
import android.content.ComponentName;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
|
@ -116,7 +114,6 @@ import net.pierrox.lightning_launcher.views.MyViewPager.OnPageChangeListener;
|
|||
import net.pierrox.lightning_launcher.views.NativeImage;
|
||||
import net.pierrox.lightning_launcher.views.NativeWallpaperView;
|
||||
import net.pierrox.lightning_launcher.views.item.ItemView;
|
||||
import net.pierrox.lightning_launcher_extreme.BuildConfig;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
@ -126,7 +123,6 @@ import org.koin.java.KoinJavaComponent;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Customize extends ResourceWrapperActivity implements
|
||||
|
@ -652,22 +648,8 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
}
|
||||
}
|
||||
|
||||
if (app.isTrialVersionExpired()) {
|
||||
app.showFeatureLockedDialog(this);
|
||||
} else {
|
||||
if ((mSystemConfig.hints & SystemConfig.HINT_CUSTOMIZE_HELP) == 0) {
|
||||
showDialog(DIALOG_HELP_HINT);
|
||||
} else if ((mSystemConfig.hints & SystemConfig.HINT_RATE) == 0 && BuildConfig.IS_TRIAL) {
|
||||
try {
|
||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
Field f = pi.getClass().getField("firstInstallTime");
|
||||
long firstInstallTime = f.getLong(pi);
|
||||
if ((System.currentTimeMillis() - firstInstallTime) > 20 * 86400 * 1000) {
|
||||
showDialog(DIALOG_RATE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1335,19 +1317,6 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
// pass
|
||||
}
|
||||
|
||||
private void setPreferenceLockFlag(ArrayList<LLPreference> preferences) {
|
||||
if (preferences != null) {
|
||||
setPreferenceLockFlag(preferences.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private void setPreferenceLockFlag(Object[] preferences) {
|
||||
final boolean need_lock = LLApp.get().isFreeVersion();
|
||||
for (Object p : preferences) {
|
||||
if (p != null && p instanceof LLPreference) ((LLPreference) p).setLocked(need_lock);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLaunchedFromAppDrawer() {
|
||||
ComponentName cn = getIntent().getParcelableExtra(INTENT_EXTRA_LAUNCHED_FROM);
|
||||
return cn != null && cn.equals(new ComponentName(this, AppDrawerX.class));
|
||||
|
@ -1659,16 +1628,6 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
|
||||
mPreferenceScreenLevel = -1;
|
||||
pushPreferenceScreen(mPreferencesPage);
|
||||
|
||||
if (is_app_drawer) {
|
||||
setPreferenceLockFlag(new LLPreference[]{mPGBackgroundSelectScreenWallpaper, mPGBackgroundScaleType, mPGZoomScrollWrapX, mPGZoomScrollWrapY, mPGFolderLook, mPGFolderFeel, mPGAppDrawerABBackground, mPGAppDrawerABDisplayOnScroll, mPGAppDrawerABHide, mPGEvents, mPGADModes, mPGAppDrawerABTextColor});
|
||||
setPreferenceLockFlag(mPreferencesPageFolderLook);
|
||||
setPreferenceLockFlag(mPreferencesPageEvents);
|
||||
setPreferenceLockFlag(mPreferencesPageFolderFeel);
|
||||
setPreferenceLockFlag(mPreferencesPageADModes);
|
||||
} else {
|
||||
setPreferenceLockFlag(new LLPreference[]{mPGBackgroundSelectScreenWallpaper, mPGZoomScrollWrapX, mPGZoomScrollWrapY});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGlobalConfig() {
|
||||
|
@ -1714,7 +1673,7 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
mPreferencesGlobalConfig.add(mGCHotwords = new LLPreferenceCheckBox(this, ID_mGCHotwords, R.string.hw_t, R.string.hw_s, mSystemConfig.hotwords, null));
|
||||
|
||||
if (mSystemConfig.expertMode) {
|
||||
mPreferencesGlobalConfig.add(mGCRunScripts = new LLPreferenceCheckBox(this, ID_mGCRunScripts, R.string.rs_t, R.string.rs_s, mGlobalConfig.runScripts && !LLApp.get().isFreeVersion(), null));
|
||||
mPreferencesGlobalConfig.add(mGCRunScripts = new LLPreferenceCheckBox(this, ID_mGCRunScripts, R.string.rs_t, R.string.rs_s, mGlobalConfig.runScripts, null));
|
||||
mPreferencesGlobalConfig.add(mGCKeepInMemory = new LLPreferenceCheckBox(this, ID_mGCKeepInMemory, R.string.keep_in_memory_t, R.string.keep_in_memory_s, mSystemConfig.keepInMemory, null));
|
||||
mPreferencesGlobalConfig.add(mGCImagePoolSize = new LLPreferenceSlider(this, ID_mGCImagePoolSize, R.string.ips_t, R.string.ips_s, mSystemConfig.imagePoolSize, null, ValueType.FLOAT, 0, 1, 0.1f, "%"));
|
||||
|
||||
|
@ -1766,22 +1725,9 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
}
|
||||
|
||||
pushPreferenceScreen(mPreferencesGlobalConfig);
|
||||
|
||||
setPreferenceLockFlag(new LLPreference[]{
|
||||
mGCRunScripts
|
||||
});
|
||||
if (LLApp.get().isFreeVersion() && getPackageManager().checkSignatures(getPackageName(), LLApp.LKP_PKG_NAME) != PackageManager.SIGNATURE_MATCH) {
|
||||
setPreferenceLockFlag(new LLPreference[]{
|
||||
mGCLockScreenCategory, mGCLockScreenSelect, mGCLockScreenLaunchUnlock
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void savePage() {
|
||||
if (LLApp.get().isTrialVersionExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPage.setModified();
|
||||
|
||||
final boolean is_folder_page = mOpenerItem != null && mOpenerItem.getClass() == Folder.class;
|
||||
|
@ -1805,10 +1751,6 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
}
|
||||
|
||||
private void saveSystemAndGlobalConfig() {
|
||||
if (LLApp.get().isTrialVersionExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
copyPreferencesToSystemAndGlobalConfiguration();
|
||||
|
||||
LLApp.get().notifySystemConfigChanged();
|
||||
|
@ -1821,7 +1763,7 @@ public class Customize extends ResourceWrapperActivity implements
|
|||
mGlobalConfig.pageAnimation = (PageAnimation) mGCPageAnimation.getValueEnum();
|
||||
if (mGCHotwords != null) mSystemConfig.hotwords = mGCHotwords.isChecked();
|
||||
if (mSystemConfig.expertMode) {
|
||||
if (!LLApp.get().isFreeVersion()) mGlobalConfig.runScripts = mGCRunScripts.isChecked();
|
||||
mGlobalConfig.runScripts = mGCRunScripts.isChecked();
|
||||
mSystemConfig.keepInMemory = mGCKeepInMemory.isChecked();
|
||||
mSystemConfig.imagePoolSize = mGCImagePoolSize.getValue();
|
||||
mSystemConfig.autoEdit = mGCAutoEdit.isChecked();
|
||||
|
|
|
@ -1399,10 +1399,7 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
Utils.startAppStore(this, targetItemPackageName);
|
||||
break;
|
||||
|
||||
case R.id.mi_kill:
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
} else {
|
||||
case R.id.mi_kill: {
|
||||
if (!checkPermissions(new String[]{Manifest.permission.KILL_BACKGROUND_PROCESSES}, new int[]{R.string.pr_r9}, REQUEST_PERMISSION_BASE)) {
|
||||
Toast.makeText(this, R.string.pr_f5, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
|
@ -1796,15 +1793,11 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
|
||||
case R.id.mi_s:
|
||||
close_bubble = false;
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
} else {
|
||||
if (targetItem != null) {
|
||||
openBubble(BUBBLE_MODE_SCRIPTS, targetItemView);
|
||||
} else {
|
||||
openBubble(BUBBLE_MODE_SCRIPTS, mBubbleItemLayout, null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.mi_ls:
|
||||
|
@ -2385,7 +2378,7 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
case DIALOG_FIRST_USE:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.first_use_title);
|
||||
builder.setMessage(LLApp.get().isTrialVersion() && BuildConfig.IS_TRIAL ? R.string.tr_w : R.string.first_use_message);
|
||||
builder.setMessage(R.string.first_use_message);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setCancelable(false);
|
||||
return builder.create();
|
||||
|
@ -3202,10 +3195,6 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
}
|
||||
|
||||
protected void addPageIndicator() {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
ItemLayout il = mScreen.getTargetOrTopmostItemLayout();
|
||||
Page page = il.getPage();
|
||||
PageIndicator item = Utils.addPageIndicator(page, mScreen.getLastTouchedAddX(), mScreen.getLastTouchedAddY(), il.getCurrentScale(), true);
|
||||
|
@ -3226,10 +3215,6 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
}
|
||||
|
||||
private void addDynamicText() {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
ItemLayout il = mScreen.getTargetOrTopmostItemLayout();
|
||||
Page page = il.getPage();
|
||||
DynamicText item = Utils.addDynamicText(page, DynamicTextConfig.Source.DATE, page.config.newOnGrid);
|
||||
|
@ -3313,10 +3298,6 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
}
|
||||
|
||||
private void addUnlocker() {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
ItemLayout il = mScreen.getTargetOrTopmostItemLayout();
|
||||
Page page = il.getPage();
|
||||
float itemLayoutScale = il.getCurrentScale();
|
||||
|
@ -3374,10 +3355,6 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
}
|
||||
|
||||
private void addBadge() {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
ItemLayout il = mScreen.getTargetOrTopmostItemLayout();
|
||||
Page page = il.getPage();
|
||||
DynamicText item = Utils.addDynamicText(page, DynamicTextConfig.Source.MISSED_CALLS, false);
|
||||
|
@ -3523,12 +3500,6 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
return;
|
||||
}
|
||||
|
||||
final LLApp app = LLApp.get();
|
||||
if (app.isTrialVersionExpired()) {
|
||||
app.showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean was_in_edit_mode = mEditMode;
|
||||
if (was_in_edit_mode) {
|
||||
leaveEditMode(false);
|
||||
|
@ -7559,16 +7530,8 @@ public class Dashboard extends ResourceWrapperActivity implements OnLongClickLis
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void launchIntent(Intent intent, ItemView itemView) {
|
||||
ComponentName cn = intent.getComponent();
|
||||
ComponentName app_drawer = new ComponentName(mContext, AppDrawerX.class);
|
||||
if (BuildConfig.IS_TRIAL && cn != null && cn.getClassName().equals(LLApp.LL_PKG_NAME + ".activities.AppDrawer")) {
|
||||
// translate app drawer intent in the trial version into app drawer intent in the extreme version
|
||||
intent.setComponent(app_drawer);
|
||||
}
|
||||
|
||||
if (itemView != null) {
|
||||
mLastLaunchAnimation = itemView.getItem().getItemConfig().launchAnimation;
|
||||
} else {
|
||||
|
|
|
@ -423,10 +423,6 @@ public class ScreenManager extends ResourceWrapperActivity implements OnClickLis
|
|||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (LLApp.get().isFreeVersion()) {
|
||||
LLApp.get().showFeatureLockedDialog(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMode == SCREEN_MODE_SELECT) {
|
||||
int page = (Integer) v.getTag();
|
||||
|
|
|
@ -582,7 +582,7 @@ public class ScriptEditor extends ResourceWrapperActivity implements View.OnClic
|
|||
mScriptText.scrollToLine(goToLine);
|
||||
}
|
||||
|
||||
if (!engine.getGlobalConfig().runScripts || LLApp.get().isFreeVersion()) {
|
||||
if (!engine.getGlobalConfig().runScripts) {
|
||||
Toast.makeText(this, R.string.rs_w, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
|
|||
private const val KEY_CAT_TEMPLATES = "tc"
|
||||
private const val KEY_TEMPLATES_BROWSE = "tb"
|
||||
private const val KEY_TEMPLATES_APPLY = "ta"
|
||||
private const val KEY_UPGRADE = "u"
|
||||
}
|
||||
|
||||
private val templateLauncher =
|
||||
|
@ -64,12 +63,12 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
|
|||
setupPreference(
|
||||
KEY_CONFIGURE_PAGES,
|
||||
R.string.configure_pages_t,
|
||||
if (app.isFreeVersion()) R.string.tr_br_s else R.string.configure_pages_s
|
||||
R.string.configure_pages_s
|
||||
)
|
||||
setupPreference(
|
||||
KEY_BACKUP_RESTORE,
|
||||
R.string.backup_restore_t,
|
||||
if (app.isTrialVersion()) R.string.tr_br_s else 0
|
||||
0
|
||||
)
|
||||
|
||||
setupPreference(KEY_CAT_INFOS, R.string.app_name, 0)
|
||||
|
@ -77,27 +76,16 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
|
|||
setupPreference(KEY_COMMUNITY, R.string.facebook_t, R.string.facebook_s)
|
||||
setupPreference(KEY_RATE, R.string.rate_t, R.string.rate_s)
|
||||
|
||||
setupPreference(KEY_UPGRADE, R.string.tr_rs_t, R.string.tr_rs_s)
|
||||
if (app.isTrialVersion()) {
|
||||
val left = app.getTrialLeft()
|
||||
val d = if (left == 0L) 0 else 1 + left / 86400000L
|
||||
findPreference<Preference>(KEY_UPGRADE)?.setSummary(getString(R.string.tr_l, d))
|
||||
}
|
||||
|
||||
setupPreference(KEY_CAT_TEMPLATES, R.string.tmpl_t, R.string.tmpl_s)
|
||||
setupPreference(KEY_TEMPLATES_BROWSE, R.string.tmpl_b_t, R.string.tmpl_b_s)
|
||||
setupPreference(KEY_TEMPLATES_APPLY, R.string.tmpl_a_t, R.string.tmpl_a_s)
|
||||
|
||||
val pc = preferenceScreen.findPreference(KEY_CAT_INFOS) as PreferenceCategory?
|
||||
val ratePreference = findPreference<Preference>(KEY_RATE)
|
||||
val upgradePreference = findPreference<Preference>(KEY_UPGRADE)
|
||||
|
||||
if (!Version.HAS_RATE_LINK || app.isFreeVersion() || app.isTrialVersion()) {
|
||||
if (!Version.HAS_RATE_LINK) {
|
||||
ratePreference?.let { pc?.removePreference(it) }
|
||||
}
|
||||
if (!app.isFreeVersion() && !app.isTrialVersion()) {
|
||||
upgradePreference?.let { pc?.removePreference(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPreference(key: String, title: Int, summary: Int) {
|
||||
|
@ -128,8 +116,6 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
|
|||
startActivity(Intent(activity, BackupRestore::class.java))
|
||||
} else if (KEY_SELECT_LAUNCHER == key) {
|
||||
PhoneUtils.selectLauncher(activity, true)
|
||||
} else if (KEY_UPGRADE == key) {
|
||||
LLApp.get().startUnlockProcess(activity)
|
||||
} else if (KEY_TEMPLATES_BROWSE == key) {
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
|
|
|
@ -55,7 +55,6 @@ import net.margaritov.preference.colorpicker.ColorPickerDialog;
|
|||
import net.margaritov.preference.colorpicker.ColorPickerDialog.OnColorChangedListener;
|
||||
import net.margaritov.preference.colorpicker.ColorPickerPanelView;
|
||||
import net.pierrox.lightning_launcher.LLApp;
|
||||
import net.pierrox.lightning_launcher.LLAppPhone;
|
||||
import net.pierrox.lightning_launcher.engine.variable.Binding;
|
||||
import net.pierrox.lightning_launcher.script.api.Property;
|
||||
import net.pierrox.lightning_launcher.views.BoxEditorView;
|
||||
|
@ -98,10 +97,6 @@ public class LLPreferenceListView extends ListView implements OnItemClickListene
|
|||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Context context = getContext();
|
||||
LLPreference p = (LLPreference) parent.getItemAtPosition(position);
|
||||
if (p.isLocked()) {
|
||||
LLApp.get().showFeatureLockedDialog(context);
|
||||
return;
|
||||
}
|
||||
if (p.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
@ -347,7 +342,6 @@ public class LLPreferenceListView extends ListView implements OnItemClickListene
|
|||
preference_view.setMinimumHeight(getResources().getDimensionPixelSize(R.dimen.pref_height));
|
||||
}
|
||||
}
|
||||
preference_view = ((LLAppPhone) LLApp.get()).managePreferenceViewLockedFlag(p, preference_view);
|
||||
View icon = preference_view.findViewById(android.R.id.icon);
|
||||
if (icon != null) {
|
||||
((View) icon.getParent()).setVisibility(View.GONE);
|
||||
|
|
|
@ -222,8 +222,6 @@ public class AddItemDialog extends AlertDialog implements View.OnClickListener,
|
|||
item.setOnClickListener(this);
|
||||
item.setTag(id);
|
||||
Utils.setEnabledStateOnViews(item, mAddItemDialogInterface.isDialogAddItemEnabled(id));
|
||||
final boolean is_locked = app.isFreeVersion() && (id == AI_BADGE || id == AI_DYNAMIC_TEXT || id == AI_PAGE_INDICATOR || id == AI_UNLOCKER || id == AI_CUSTOM_VIEW);
|
||||
app.manageAddItemDialogLockedFlag(item, is_locked);
|
||||
root.addView(item);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ import android.view.animation.AnimationUtils;
|
|||
import android.widget.TextView;
|
||||
|
||||
import net.pierrox.lightning_launcher.LLApp;
|
||||
import net.pierrox.lightning_launcher.LLAppExtreme;
|
||||
import net.pierrox.lightning_launcher.LWPSettings;
|
||||
import net.pierrox.lightning_launcher.activities.ResourcesWrapperHelper;
|
||||
import net.pierrox.lightning_launcher.api.ScreenIdentity;
|
||||
|
@ -280,9 +279,6 @@ public class LightningLWPService extends WallpaperService {
|
|||
}
|
||||
|
||||
private void configurePage() {
|
||||
if (!((LLAppExtreme) LLApp.get()).hasLWP()) {
|
||||
mLWPDesktopId = Page.NONE;
|
||||
}
|
||||
boolean hasPage = mLWPDesktopId != Page.NONE;
|
||||
mContentView.findViewById(R.id.empty_c).setVisibility(hasPage ? View.GONE : View.VISIBLE);
|
||||
LightningEngine lightningEngine = LLApp.get().getAppEngine();
|
||||
|
|
|
@ -64,7 +64,6 @@ import net.pierrox.lightning_launcher.data.Utils;
|
|||
import net.pierrox.lightning_launcher.engine.LightningEngine;
|
||||
import net.pierrox.lightning_launcher.feature.settings.RootSettings;
|
||||
import net.pierrox.lightning_launcher.script.ScriptManager;
|
||||
import net.pierrox.lightning_launcher_extreme.BuildConfig;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -118,12 +117,10 @@ public class Setup {
|
|||
final LightningEngine engine = LLApp.get().getAppEngine();
|
||||
|
||||
Context tmp = null;
|
||||
if (!BuildConfig.IS_TRIAL) {
|
||||
try {
|
||||
tmp = LLApp.get().createPackageContext(LLApp.LL_PKG_NAME, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
}
|
||||
final Context classic_ll_context = tmp;
|
||||
|
||||
final boolean import_data = false; //classic_ll_context!=null;
|
||||
|
@ -212,7 +209,6 @@ public class Setup {
|
|||
|
||||
GlobalConfig globalConfig = engine.getGlobalConfig();
|
||||
|
||||
if (!BuildConfig.IS_TRIAL || LLApp.get().isTrialVersion()) {
|
||||
Page lockscreen = engine.getOrLoadPage(Page.FIRST_DASHBOARD_PAGE + 1);
|
||||
setupLockScreen(lockscreen);
|
||||
|
||||
|
@ -221,10 +217,6 @@ public class Setup {
|
|||
|
||||
globalConfig.screensOrder[1] = lockscreen.id;
|
||||
globalConfig.screensNames[1] = engine.getContext().getString(R.string.ds_1);
|
||||
} else {
|
||||
globalConfig.screensOrder = new int[1];
|
||||
globalConfig.screensNames = new String[1];
|
||||
}
|
||||
|
||||
// create dashboard default config
|
||||
int page = Page.FIRST_DASHBOARD_PAGE;
|
||||
|
@ -294,7 +286,6 @@ public class Setup {
|
|||
ic.box_s = ic.box.toString(dashboard_config.defaultItemConfig.box);
|
||||
pi.notifyChanged();
|
||||
|
||||
if (!LLApp.get().isFreeVersion()) {
|
||||
// install date and time dynamic texts
|
||||
DynamicText dt = Utils.addDynamicText(dashboard, DynamicTextConfig.Source.DATE, true);
|
||||
dt.getCell().set(3, 4, 5, 5);
|
||||
|
@ -323,10 +314,6 @@ public class Setup {
|
|||
sc = dt.modifyShortcutConfig();
|
||||
sc.labelFontSize = displayMetrics.widthPixels / (25 * density);
|
||||
dt.notifyChanged();
|
||||
}
|
||||
|
||||
// install promotional icons such as mgoid, baby games, mcompass, let's dance
|
||||
LLApp.get().installPromotionalIcons(dashboard);
|
||||
|
||||
// various shortcuts (backup/restore, all settings, script editor)
|
||||
installShortcut(dashboard, -5, 0, "N", resources.getString(R.string.mi_es_settings), null, new Intent(context, RootSettings.class));
|
||||
|
|
|
@ -55,9 +55,6 @@
|
|||
<Preference
|
||||
android:key="s"/>
|
||||
|
||||
<Preference
|
||||
android:key="u"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
|
||||
<application>
|
||||
<activity android:name="net.pierrox.lightning_launcher.activities.AppUnlocker"
|
||||
android:theme="@style/AppDialog"/>
|
||||
|
||||
<provider android:name="net.pierrox.lightning_launcher.util.ApiProvider" android:authorities="${provider_authority}"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
</manifest>
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.vending.billing;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
|
||||
* This service provides the following features:
|
||||
* 1. Provides a new API to get details of in-app items published for the app including
|
||||
* price, type, title and description.
|
||||
* 2. The purchase flow is synchronous and purchase information is available immediately
|
||||
* after it completes.
|
||||
* 3. Purchase information of in-app purchases is maintained within the Google Play system
|
||||
* till the purchase is consumed.
|
||||
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
|
||||
* in-app items are consumable and thereafter can be purchased again.
|
||||
* 5. An API to get current purchases of the user immediately. This will not contain any
|
||||
* consumed purchases.
|
||||
*
|
||||
* All calls will give a response code with the following possible values
|
||||
* RESULT_OK = 0 - success
|
||||
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
|
||||
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
|
||||
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
|
||||
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
|
||||
* RESULT_ERROR = 6 - Fatal error during the API action
|
||||
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
|
||||
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
|
||||
*/
|
||||
interface IInAppBillingService {
|
||||
/**
|
||||
* Checks support for the requested billing API version, package and in-app type.
|
||||
* Minimum API version supported by this interface is 3.
|
||||
* @param apiVersion the billing version which the app is using
|
||||
* @param packageName the package name of the calling app
|
||||
* @param type type of the in-app item being purchased "inapp" for one-time purchases
|
||||
* and "subs" for subscription.
|
||||
* @return RESULT_OK(0) on success, corresponding result code on failures
|
||||
*/
|
||||
int isBillingSupported(int apiVersion, String packageName, String type);
|
||||
|
||||
/**
|
||||
* Provides details of a list of SKUs
|
||||
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
|
||||
* with a list JSON strings containing the productId, price, title and description.
|
||||
* This API can be called with a maximum of 20 SKUs.
|
||||
* @param apiVersion billing API version that the Third-party is using
|
||||
* @param packageName the package name of the calling app
|
||||
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "DETAILS_LIST" with a StringArrayList containing purchase information
|
||||
* in JSON format similar to:
|
||||
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
|
||||
* "title : "Example Title", "description" : "This is an example description" }'
|
||||
*/
|
||||
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
|
||||
|
||||
/**
|
||||
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
|
||||
* the type, a unique purchase token and an optional developer payload.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param sku the SKU of the in-app item as published in the developer console
|
||||
* @param type the type of the in-app item ("inapp" for one-time purchases
|
||||
* and "subs" for subscription).
|
||||
* @param developerPayload optional argument to be sent back with the purchase information
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "BUY_INTENT" - PendingIntent to start the purchase flow
|
||||
*
|
||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
|
||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
|
||||
* If the purchase is successful, the result data will contain the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
|
||||
* '{"orderId":"12999763169054705758.1371079406387615",
|
||||
* "packageName":"com.example.app",
|
||||
* "productId":"exampleSku",
|
||||
* "purchaseTime":1345678900000,
|
||||
* "purchaseToken" : "122333444455555",
|
||||
* "developerPayload":"example developer payload" }'
|
||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
|
||||
* was signed with the private key of the developer
|
||||
* TODO: change this to app-specific keys.
|
||||
*/
|
||||
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
|
||||
String developerPayload);
|
||||
|
||||
/**
|
||||
* Returns the current SKUs owned by the user of the type and package name specified along with
|
||||
* purchase information and a signature of the data to be validated.
|
||||
* This will return all SKUs that have been purchased in V3 and managed items purchased using
|
||||
* V1 and V2 that have not been consumed.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param type the type of the in-app items being requested
|
||||
* ("inapp" for one-time purchases and "subs" for subscription).
|
||||
* @param continuationToken to be set as null for the first call, if the number of owned
|
||||
* skus are too many, a continuationToken is returned in the response bundle.
|
||||
* This method can be called again with the continuation token to get the next set of
|
||||
* owned skus.
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
|
||||
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
|
||||
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
|
||||
* of the purchase information
|
||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
|
||||
* next set of in-app purchases. Only set if the
|
||||
* user has more owned skus than the current list.
|
||||
*/
|
||||
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
|
||||
|
||||
/**
|
||||
* Consume the last purchase of the given SKU. This will result in this item being removed
|
||||
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param purchaseToken token in the purchase information JSON that identifies the purchase
|
||||
* to be consumed
|
||||
* @return 0 if consumption succeeded. Appropriate error values for failures.
|
||||
*/
|
||||
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
|
||||
}
|
|
@ -1,346 +0,0 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pierre Hébert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pierre Hébert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import net.pierrox.lightning_launcher.LLAppPhone;
|
||||
import net.pierrox.lightning_launcher.activities.AppUnlocker;
|
||||
import net.pierrox.lightning_launcher.data.FileUtils;
|
||||
import net.pierrox.lightning_launcher.data.Page;
|
||||
import net.pierrox.lightning_launcher.data.Shortcut;
|
||||
import net.pierrox.lightning_launcher.data.Utils;
|
||||
import net.pierrox.lightning_launcher.iab.IabHelper;
|
||||
import net.pierrox.lightning_launcher.iab.IabResult;
|
||||
import net.pierrox.lightning_launcher.iab.Inventory;
|
||||
import net.pierrox.lightning_launcher.iab.Purchase;
|
||||
import net.pierrox.lightning_launcher.prefs.LLPreference;
|
||||
import net.pierrox.lightning_launcher_extreme.BuildConfig;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LLAppTrial extends LLAppPhone {
|
||||
|
||||
// public key obfuscated tr "MIAQuk" "*<_=()" part 1
|
||||
private static final String SCREEN = "DOe78xjw(jymH98)VG+vHOExCvdl*2K62vw5FO50Nf9DGU(LWRR(R0<1_g8C_syfFNVgrtv5BPpU3tzgBb<XL)gdD";
|
||||
private static final String DRAWER = "qHo/EJ)=r)m1beW92FnBxFFRLaF=xh=)hPds=<D_=_B";
|
||||
// obfuscated: IAB public key
|
||||
public static String ICON_LOADER_DATA;
|
||||
|
||||
private static final long TRIAL_SHIFT = 365L*2*86400*1000;
|
||||
private static final long TRIAL_DURATION = 7L*86400*1000 + 1987; //1987 to avoid clear text 7days
|
||||
|
||||
private boolean mIsOldFreeUser;
|
||||
private boolean mIsUnlocked;
|
||||
private long mTrialPeriodStartDate;
|
||||
|
||||
private IabHelper mIABHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
generateKey();
|
||||
|
||||
setupInstallType(mUpgradingOldFreeVersion);
|
||||
|
||||
checkUnlocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
|
||||
if(mIABHelper != null) {
|
||||
mIABHelper.dispose();
|
||||
mIABHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private File getDataFile() {
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), "Android");
|
||||
dir.mkdirs();
|
||||
return new File(dir, ".mnd");
|
||||
}
|
||||
|
||||
private void setupInstallType(boolean upgrading_old_free_version) {
|
||||
File data = getDataFile();
|
||||
|
||||
Boolean is_old_free_user = null;
|
||||
if(data.exists()) {
|
||||
JSONObject o = FileUtils.readJSONObjectFromFile(data);
|
||||
if(o != null) {
|
||||
try {
|
||||
if (o.has("f")) {
|
||||
is_old_free_user = o.getBoolean("f");
|
||||
if (!is_old_free_user) {
|
||||
mTrialPeriodStartDate = o.getLong("s") + TRIAL_SHIFT;
|
||||
if (getTrialLeft() > 7 * 86400000L) {
|
||||
mTrialPeriodStartDate = 0;
|
||||
}
|
||||
mIsUnlocked = o.getBoolean("u");
|
||||
}
|
||||
}
|
||||
if(o.has("u")) {
|
||||
mIsUnlocked = o.getBoolean("u");
|
||||
}
|
||||
} catch(JSONException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(is_old_free_user == null) {
|
||||
mTrialPeriodStartDate = System.currentTimeMillis();
|
||||
is_old_free_user = upgrading_old_free_version;
|
||||
|
||||
try {
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("f", is_old_free_user);
|
||||
if(!is_old_free_user) {
|
||||
o.put("s", mTrialPeriodStartDate - TRIAL_SHIFT);
|
||||
}
|
||||
FileUtils.saveStringToFile(o.toString(), data);
|
||||
} catch(Exception e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
mIsOldFreeUser = is_old_free_user;
|
||||
}
|
||||
|
||||
private void checkUnlocked() {
|
||||
mIABHelper = new IabHelper(this, ICON_LOADER_DATA);
|
||||
mIABHelper.enableDebugLogging(BuildConfig.DEBUG);
|
||||
mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
|
||||
public void onIabSetupFinished(IabResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Have we been disposed of in the meantime? If so, quit.
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
|
||||
ArrayList<String> skus = new ArrayList<String>();
|
||||
final String iab_unlock_free = getString(R.string.iab_unlock_free);
|
||||
final String iab_unlock_trial = getString(R.string.iab_unlock_trial);
|
||||
skus.add(iab_unlock_free);
|
||||
skus.add(iab_unlock_trial);
|
||||
|
||||
mIABHelper.queryInventoryAsync(true, skus, new IabHelper.QueryInventoryFinishedListener() {
|
||||
@Override
|
||||
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
|
||||
if (result.isFailure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Purchase unlock_free = inventory.getPurchase(iab_unlock_free);
|
||||
Purchase unlock_trial = inventory.getPurchase(iab_unlock_trial);
|
||||
boolean unlocked_free = unlock_free != null /*&& verifyDeveloperPayload(unlock_free)*/;
|
||||
boolean unlocked_trial = unlock_trial != null /*&& verifyDeveloperPayload(unlock_trial)*/;
|
||||
if(unlocked_free || unlocked_trial) {
|
||||
setUnlocked(true);
|
||||
} else {
|
||||
setUnlocked(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setUnlocked(boolean unlocked) {
|
||||
mIsUnlocked = unlocked;
|
||||
File data = getDataFile();
|
||||
JSONObject o = new JSONObject();
|
||||
try {
|
||||
o.put("f", mIsOldFreeUser);
|
||||
if(!mIsOldFreeUser) {
|
||||
o.put("s", mTrialPeriodStartDate - TRIAL_SHIFT);
|
||||
}
|
||||
o.put("u", mIsUnlocked);
|
||||
FileUtils.saveStringToFile(o.toString(), data);
|
||||
} catch (Exception e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreeVersion() {
|
||||
// should return true if existing setup and not unlocked
|
||||
LLAppTrial app = (LLAppTrial) LLAppTrial.get();
|
||||
return app.mIsOldFreeUser && !app.mIsUnlocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersion() {
|
||||
// should return true if new setup and not unlocked
|
||||
LLAppTrial app = (LLAppTrial) LLAppTrial.get();
|
||||
return !app.mIsOldFreeUser && !app.mIsUnlocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersionExpired() {
|
||||
// should return true if new setup and not unlocked and delay expired
|
||||
if(!isTrialVersion()) return false;
|
||||
final long diff = System.currentTimeMillis() - mTrialPeriodStartDate;
|
||||
return diff < 0 || diff > TRIAL_DURATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTrialLeft() {
|
||||
final long left = (mTrialPeriodStartDate+TRIAL_DURATION) - System.currentTimeMillis();
|
||||
return left < 0 ? 0 : left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View managePreferenceViewLockedFlag(LLPreference preference, View preference_view) {
|
||||
if (preference.isLocked()) {
|
||||
final Context context = preference_view.getContext();
|
||||
FrameLayout fl = new FrameLayout(context);
|
||||
ImageView locked = new ImageView(context);
|
||||
locked.setImageResource(R.drawable.full);
|
||||
locked.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP | Gravity.RIGHT));
|
||||
fl.addView(preference_view);
|
||||
fl.addView(locked);
|
||||
return fl;
|
||||
} else {
|
||||
return preference_view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageAddItemDialogLockedFlag(View add_item_view, boolean locked) {
|
||||
add_item_view.findViewById(R.id.full).setVisibility(locked ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFeatureLockedDialog(final Context context) {
|
||||
// need an activity context to add a window
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.tr_fl_t);
|
||||
builder.setMessage(R.string.tr_fl_m);
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startUnlockProcess(context);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUnlockProcess(Context context) {
|
||||
context.startActivity(new Intent(context, AppUnlocker.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installPromotionalIcons(Page dashboard) {
|
||||
Drawable download_d = getResources().getDrawable(R.drawable.download);
|
||||
installPromotionalIcon(dashboard, download_d, 0, 3, R.drawable.p2, "Mini Golf'oid", "net.pierrox.mini_golfoid_free");
|
||||
installPromotionalIcon(dashboard, download_d, 1, 3, R.drawable.p1, "Baby's Games", "net.pierrox.baby_games");
|
||||
installPromotionalIcon(dashboard, download_d, 2, 3, R.drawable.p3, "Marine Compass", "net.pierrox.mcompass");
|
||||
installPromotionalIcon(dashboard, download_d, 3, 3, R.drawable.p4, "Let's Dance!", "net.pierrox.lets_dance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkLicense() {
|
||||
// pass
|
||||
}
|
||||
|
||||
private void installPromotionalIcon(Page dashboard, Drawable download_d, int x, int y, int icon, String title, String pkg_name) {
|
||||
Shortcut new_item=new Shortcut();
|
||||
int new_id=dashboard.findFreeItemId();
|
||||
File icon_dir=dashboard.getAndCreateIconDir();
|
||||
icon_dir.mkdirs();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Version.APP_STORE_INSTALL_PREFIX+pkg_name));
|
||||
new_item.init(new_id, new Rect(x, y, x+1, y+1), null, title, intent, dashboard.config, icon_dir);
|
||||
int size = Utils.getStandardIconSize();
|
||||
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
Drawable icon_d = getResources().getDrawable(icon);
|
||||
icon_d.setBounds(0, 0, size, size);
|
||||
icon_d.draw(canvas);
|
||||
download_d.setBounds(0, 0, size, size);
|
||||
download_d.draw(canvas);
|
||||
Utils.saveIconToFile(new_item.getDefaultIconFile(icon_dir), bitmap);
|
||||
dashboard.addItem(new_item);
|
||||
}
|
||||
|
||||
// compute the IAB public key from obfuscated data
|
||||
private static void generateKey() {
|
||||
ICON_LOADER_DATA = AppUnlocker.ICON_DATA + "t" + SCREEN + "9" + DRAWER;
|
||||
String tr = "*M<I_A=Q(u)k";
|
||||
final int length = tr.length();
|
||||
for(int i=0; i<length;i+=2) {
|
||||
ICON_LOADER_DATA = ICON_LOADER_DATA.replace(tr.charAt(i), tr.charAt(i+1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pierre Hébert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.pierrox.lightning_launcher.LLApp;
|
||||
import net.pierrox.lightning_launcher.LLAppTrial;
|
||||
import net.pierrox.lightning_launcher.activities.ResourceWrapperActivity;
|
||||
import net.pierrox.lightning_launcher.iab.IabHelper;
|
||||
import net.pierrox.lightning_launcher.iab.IabResult;
|
||||
import net.pierrox.lightning_launcher.iab.Inventory;
|
||||
import net.pierrox.lightning_launcher.iab.Purchase;
|
||||
import net.pierrox.lightning_launcher_extreme.BuildConfig;
|
||||
import net.pierrox.lightning_launcher_extreme.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AppUnlocker extends ResourceWrapperActivity {
|
||||
public static final String ICON_DATA = "*<Gf*_0GCSqGS<b3D=EB_=U__4GN_DCBi=KBg=CD4txD(TS(wSCVRGr*e7ZVFjhTPSpDgoBlH_O8glTaTP";
|
||||
|
||||
private static final int REQUEST_PURCHASE_UNLOCK = 1;
|
||||
|
||||
private String SKU_UNLOCK_FREE;
|
||||
private String SKU_UNLOCK_TRIAL;
|
||||
|
||||
private IabHelper mIABHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
SKU_UNLOCK_FREE = getString(R.string.iab_unlock_free);
|
||||
SKU_UNLOCK_TRIAL = getString(R.string.iab_unlock_trial);
|
||||
|
||||
setContentView(R.layout.app_unlocker);
|
||||
|
||||
((TextView)findViewById(R.id.au_msg)).setText(R.string.tr_eu);
|
||||
((TextView)findViewById(R.id.au_pw)).setText(R.string.tr_pw);
|
||||
findViewById(R.id.au_ok).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setWaitScreen(true, 0);
|
||||
|
||||
String base64EncodedPublicKey = LLAppTrial.ICON_LOADER_DATA;
|
||||
mIABHelper = new IabHelper(this, base64EncodedPublicKey);
|
||||
mIABHelper.enableDebugLogging(BuildConfig.DEBUG);
|
||||
mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
|
||||
public void onIabSetupFinished(IabResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
// Have we been disposed of in the meantime? If so, quit.
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
|
||||
ArrayList<String> skus = new ArrayList<String>();
|
||||
skus.add(SKU_UNLOCK_FREE);
|
||||
skus.add(SKU_UNLOCK_TRIAL);
|
||||
mIABHelper.queryInventoryAsync(true, skus, mGotInventoryListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (mIABHelper != null) {
|
||||
mIABHelper.dispose();
|
||||
mIABHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if(mIABHelper == null) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
if (!mIABHelper.handleActivityResult(requestCode, resultCode, data)) {
|
||||
// not handled, so handle it ourselves (here's where you'd
|
||||
// perform any handling of activity results not related to in-app
|
||||
// billing...
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void purchaseUnlock() {
|
||||
mIABHelper.launchPurchaseFlow(this, SKU_UNLOCK_FREE, REQUEST_PURCHASE_UNLOCK, mPurchaseFinishedListener, "");
|
||||
}
|
||||
|
||||
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
|
||||
@Override
|
||||
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
// Is it a failure?
|
||||
if (result.isFailure()) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
Purchase unlock_free = inventory.getPurchase(SKU_UNLOCK_FREE);
|
||||
Purchase unlock_trial = inventory.getPurchase(SKU_UNLOCK_TRIAL);
|
||||
boolean unlocked_free = unlock_free != null && verifyDeveloperPayload(unlock_free);
|
||||
boolean unlocked_trial = unlock_trial != null && verifyDeveloperPayload(unlock_trial);
|
||||
if(unlocked_free || unlocked_trial) {
|
||||
setUnlocked(true);
|
||||
setWaitScreen(false, R.string.tr_ty);
|
||||
} else {
|
||||
mIABHelper.launchPurchaseFlow(AppUnlocker.this, LLApp.get().isFreeVersion() ? SKU_UNLOCK_FREE : SKU_UNLOCK_TRIAL, REQUEST_PURCHASE_UNLOCK, mPurchaseFinishedListener);
|
||||
setUnlocked(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
|
||||
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
|
||||
if (mIABHelper == null) return;
|
||||
|
||||
if (result.isFailure()) {
|
||||
setWaitScreen(false, result.getResponse() == IabHelper.IABHELPER_USER_CANCELLED ? R.string.tr_c : R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
if (!verifyDeveloperPayload(purchase)) {
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
return;
|
||||
}
|
||||
|
||||
final String sku = purchase.getSku();
|
||||
if (sku.equals(SKU_UNLOCK_FREE) || sku.equals(SKU_UNLOCK_TRIAL)) {
|
||||
setUnlocked(true);
|
||||
setWaitScreen(false, R.string.tr_ty);
|
||||
} else {
|
||||
setUnlocked(false);
|
||||
setWaitScreen(false, R.string.tr_eu);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void setWaitScreen(boolean on, int msg_res_id) {
|
||||
findViewById(R.id.au_d).setVisibility(on ? View.INVISIBLE : View.VISIBLE);
|
||||
findViewById(R.id.au_w).setVisibility(on ? View.VISIBLE : View.INVISIBLE);
|
||||
if(msg_res_id != 0) {
|
||||
((TextView)findViewById(R.id.au_msg)).setText(msg_res_id);
|
||||
}
|
||||
}
|
||||
|
||||
private void setUnlocked(boolean unlocked) {
|
||||
((LLAppTrial)LLApp.get()).setUnlocked(unlocked);
|
||||
}
|
||||
|
||||
/** Verifies the developer payload of a purchase. */
|
||||
boolean verifyDeveloperPayload(Purchase p) {
|
||||
String payload = p.getDeveloperPayload();
|
||||
|
||||
/*
|
||||
* TODO: verify that the developer payload of the purchase is correct. It will be
|
||||
* the same one that you sent when initiating the purchase.
|
||||
*
|
||||
* WARNING: Locally generating a random string when starting a purchase and
|
||||
* verifying it here might seem like a good approach, but this will fail in the
|
||||
* case where the user purchases an item on one device and then uses your app on
|
||||
* a different device, because on the other device you will not have access to the
|
||||
* random string you originally generated.
|
||||
*
|
||||
* So a good developer payload has these characteristics:
|
||||
*
|
||||
* 1. If two different users purchase an item, the payload is different between them,
|
||||
* so that one user's purchase can't be replayed to another user.
|
||||
*
|
||||
* 2. The payload must be such that you can verify it even when the app wasn't the
|
||||
* one who initiated the purchase flow (so that items purchased by the user on
|
||||
* one device work on other devices owned by the user).
|
||||
*
|
||||
* Using your own server to store and verify developer payloads across app
|
||||
* installations is recommended.
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,570 +0,0 @@
|
|||
// Portions copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
// This code was converted from code at http://iharder.sourceforge.net/base64/
|
||||
// Lots of extraneous features were removed.
|
||||
/* The original code said:
|
||||
* <p>
|
||||
* I am placing this code in the Public Domain. Do with it as you will.
|
||||
* This software comes with no guarantees or warranties but with
|
||||
* plenty of well-wishing instead!
|
||||
* Please visit
|
||||
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
|
||||
* periodically to check for updates or to contribute improvements.
|
||||
* </p>
|
||||
*
|
||||
* @author Robert Harder
|
||||
* @author rharder@usa.net
|
||||
* @version 1.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base64 converter class. This code is not a complete MIME encoder;
|
||||
* it simply converts binary data to base64 data and back.
|
||||
*
|
||||
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
|
||||
* class.
|
||||
*/
|
||||
public class Base64 {
|
||||
/** Specify encoding (value is {@code true}). */
|
||||
public final static boolean ENCODE = true;
|
||||
|
||||
/** Specify decoding (value is {@code false}). */
|
||||
public final static boolean DECODE = false;
|
||||
|
||||
/** The equals sign (=) as a byte. */
|
||||
private final static byte EQUALS_SIGN = (byte) '=';
|
||||
|
||||
/** The new line character (\n) as a byte. */
|
||||
private final static byte NEW_LINE = (byte) '\n';
|
||||
|
||||
/**
|
||||
* The 64 valid Base64 values.
|
||||
*/
|
||||
private final static byte[] ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '+', (byte) '/'};
|
||||
|
||||
/**
|
||||
* The 64 valid web safe Base64 values.
|
||||
*/
|
||||
private final static byte[] WEBSAFE_ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '-', (byte) '_'};
|
||||
|
||||
/**
|
||||
* Translates a Base64 value to either its 6-bit reconstruction value
|
||||
* or a negative number indicating some other meaning.
|
||||
**/
|
||||
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
|
||||
62, // Plus sign at decimal 43
|
||||
-9, -9, -9, // Decimal 44 - 46
|
||||
63, // Slash at decimal 47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
/** The web safe decodabet */
|
||||
private final static byte[] WEBSAFE_DECODABET =
|
||||
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
|
||||
62, // Dash '-' sign at decimal 45
|
||||
-9, -9, // Decimal 46-47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, // Decimal 91-94
|
||||
63, // Underscore '_' at decimal 95
|
||||
-9, // Decimal 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
// Indicates white space in encoding
|
||||
private final static byte WHITE_SPACE_ENC = -5;
|
||||
// Indicates equals sign in encoding
|
||||
private final static byte EQUALS_SIGN_ENC = -1;
|
||||
|
||||
/** Defeats instantiation. */
|
||||
private Base64() {
|
||||
}
|
||||
|
||||
/* ******** E N C O D I N G M E T H O D S ******** */
|
||||
|
||||
/**
|
||||
* Encodes up to three bytes of the array <var>source</var>
|
||||
* and writes the resulting four Base64 bytes to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 3 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 4 for
|
||||
* the <var>destination</var> array.
|
||||
* The actual number of significant bytes in your array is
|
||||
* given by <var>numSigBytes</var>.
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param numSigBytes the number of significant bytes in your array
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @return the <var>destination</var> array
|
||||
* @since 1.3
|
||||
*/
|
||||
private static byte[] encode3to4(byte[] source, int srcOffset,
|
||||
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901 Bit position
|
||||
// --------000000001111111122222222 Array position from threeBytes
|
||||
// --------| || || || | Six bit groups to index alphabet
|
||||
// >>18 >>12 >> 6 >> 0 Right shift necessary
|
||||
// 0x3f 0x3f 0x3f Additional AND
|
||||
|
||||
// Create buffer with zero-padding if there are only one or two
|
||||
// significant bytes passed in the array.
|
||||
// We have to shift left 24 in order to flush out the 1's that appear
|
||||
// when Java treats a value as negative that is cast from a byte to an int.
|
||||
int inBuff =
|
||||
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
|
||||
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
|
||||
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
|
||||
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
|
||||
return destination;
|
||||
case 2:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
case 1:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = EQUALS_SIGN;
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
default:
|
||||
return destination;
|
||||
} // end switch
|
||||
} // end encode3to4
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
* Equivalent to calling
|
||||
* {@code encodeBytes(source, 0, source.length)}
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return encode(source, 0, source.length, ALPHABET, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into web safe Base64 notation.
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
*/
|
||||
public static String encodeWebSafe(byte[] source, boolean doPadding) {
|
||||
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet the encoding alphabet
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
boolean doPadding) {
|
||||
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
|
||||
int outLen = outBuff.length;
|
||||
|
||||
// If doPadding is false, set length to truncate '='
|
||||
// padding characters
|
||||
while (doPadding == false && outLen > 0) {
|
||||
if (outBuff[outLen - 1] != '=') {
|
||||
break;
|
||||
}
|
||||
outLen -= 1;
|
||||
}
|
||||
|
||||
return new String(outBuff, 0, outLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @param maxLineLength maximum length of one line.
|
||||
* @return the BASE64-encoded byte array
|
||||
*/
|
||||
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
int maxLineLength) {
|
||||
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
|
||||
int len43 = lenDiv3 * 4;
|
||||
byte[] outBuff = new byte[len43 // Main 4:3
|
||||
+ (len43 / maxLineLength)]; // New lines
|
||||
|
||||
int d = 0;
|
||||
int e = 0;
|
||||
int len2 = len - 2;
|
||||
int lineLength = 0;
|
||||
for (; d < len2; d += 3, e += 4) {
|
||||
|
||||
// The following block of code is the same as
|
||||
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
|
||||
// but inlined for faster encoding (~20% improvement)
|
||||
int inBuff =
|
||||
((source[d + off] << 24) >>> 8)
|
||||
| ((source[d + 1 + off] << 24) >>> 16)
|
||||
| ((source[d + 2 + off] << 24) >>> 24);
|
||||
outBuff[e] = alphabet[(inBuff >>> 18)];
|
||||
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
lineLength = 0;
|
||||
} // end if: end of line
|
||||
} // end for: each piece of array
|
||||
|
||||
if (d < len) {
|
||||
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
// Add a last newline
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
}
|
||||
e += 4;
|
||||
}
|
||||
|
||||
assert (e == outBuff.length);
|
||||
return outBuff;
|
||||
}
|
||||
|
||||
|
||||
/* ******** D E C O D I N G M E T H O D S ******** */
|
||||
|
||||
|
||||
/**
|
||||
* Decodes four bytes from array <var>source</var>
|
||||
* and writes the resulting bytes (up to three of them)
|
||||
* to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 4 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 3 for
|
||||
* the <var>destination</var> array.
|
||||
* This method returns the actual number of bytes that
|
||||
* were converted from the Base64 encoding.
|
||||
*
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return the number of decoded bytes converted
|
||||
* @since 1.3
|
||||
*/
|
||||
private static int decode4to3(byte[] source, int srcOffset,
|
||||
byte[] destination, int destOffset, byte[] decodabet) {
|
||||
// Example: Dk==
|
||||
if (source[srcOffset + 2] == EQUALS_SIGN) {
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
return 1;
|
||||
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >>> 8);
|
||||
return 2;
|
||||
} else {
|
||||
// Example: DkLE
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
|
||||
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >> 8);
|
||||
destination[destOffset + 2] = (byte) (outBuff);
|
||||
return 3;
|
||||
}
|
||||
} // end decodeToBytes
|
||||
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation.
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decode(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from web safe Base64 notation.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decodeWebSafe(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source The Base64 encoded data
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source) throws Base64DecoderException {
|
||||
return decode(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded data.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source)
|
||||
throws Base64DecoderException {
|
||||
return decodeWebSafe(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, WEBSAFE_DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content using the supplied decodabet and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
|
||||
throws Base64DecoderException {
|
||||
int len34 = len * 3 / 4;
|
||||
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
|
||||
int outBuffPosn = 0;
|
||||
|
||||
byte[] b4 = new byte[4];
|
||||
int b4Posn = 0;
|
||||
int i = 0;
|
||||
byte sbiCrop = 0;
|
||||
byte sbiDecode = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
|
||||
sbiDecode = decodabet[sbiCrop];
|
||||
|
||||
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
|
||||
if (sbiDecode >= EQUALS_SIGN_ENC) {
|
||||
// An equals sign (for padding) must not occur at position 0 or 1
|
||||
// and must be the last byte[s] in the encoded value
|
||||
if (sbiCrop == EQUALS_SIGN) {
|
||||
int bytesLeft = len - i;
|
||||
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
|
||||
if (b4Posn == 0 || b4Posn == 1) {
|
||||
throw new Base64DecoderException(
|
||||
"invalid padding byte '=' at byte offset " + i);
|
||||
} else if ((b4Posn == 3 && bytesLeft > 2)
|
||||
|| (b4Posn == 4 && bytesLeft > 1)) {
|
||||
throw new Base64DecoderException(
|
||||
"padding byte '=' falsely signals end of encoded value "
|
||||
+ "at offset " + i);
|
||||
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
|
||||
throw new Base64DecoderException(
|
||||
"encoded value has invalid trailing byte");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
b4[b4Posn++] = sbiCrop;
|
||||
if (b4Posn == 4) {
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
b4Posn = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Base64DecoderException("Bad Base64 input character at " + i
|
||||
+ ": " + source[i + off] + "(decimal)");
|
||||
}
|
||||
}
|
||||
|
||||
// Because web safe encoding allows non padding base64 encodes, we
|
||||
// need to pad the rest of the b4 buffer with equal signs when
|
||||
// b4Posn != 0. There can be at most 2 equal signs at the end of
|
||||
// four characters, so the b4 buffer must have two or three
|
||||
// characters. This also catches the case where the input is
|
||||
// padded with EQUALS_SIGN
|
||||
if (b4Posn != 0) {
|
||||
if (b4Posn == 1) {
|
||||
throw new Base64DecoderException("single trailing character at offset "
|
||||
+ (len - 1));
|
||||
}
|
||||
b4[b4Posn++] = EQUALS_SIGN;
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
}
|
||||
|
||||
byte[] out = new byte[outBuffPosn];
|
||||
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Exception thrown when encountering an invalid Base64 input character.
|
||||
*
|
||||
* @author nelson
|
||||
*/
|
||||
public class Base64DecoderException extends Exception {
|
||||
public Base64DecoderException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Base64DecoderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Exception thrown when something went wrong with in-app billing.
|
||||
* An IabException has an associated IabResult (an error).
|
||||
* To get the IAB result that caused this exception to be thrown,
|
||||
* call {@link #getResult()}.
|
||||
*/
|
||||
public class IabException extends Exception {
|
||||
IabResult mResult;
|
||||
|
||||
public IabException(IabResult r) {
|
||||
this(r, null);
|
||||
}
|
||||
public IabException(int response, String message) {
|
||||
this(new IabResult(response, message));
|
||||
}
|
||||
public IabException(IabResult r, Exception cause) {
|
||||
super(r.getMessage(), cause);
|
||||
mResult = r;
|
||||
}
|
||||
public IabException(int response, String message, Exception cause) {
|
||||
this(new IabResult(response, message), cause);
|
||||
}
|
||||
|
||||
/** Returns the IAB result (error) that this exception signals. */
|
||||
public IabResult getResult() { return mResult; }
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
/**
|
||||
* Represents the result of an in-app billing operation.
|
||||
* A result is composed of a response code (an integer) and possibly a
|
||||
* message (String). You can get those by calling
|
||||
* {@link #getResponse} and {@link #getMessage()}, respectively. You
|
||||
* can also inquire whether a result is a success or a failure by
|
||||
* calling {@link #isSuccess()} and {@link #isFailure()}.
|
||||
*/
|
||||
public class IabResult {
|
||||
int mResponse;
|
||||
String mMessage;
|
||||
|
||||
public IabResult(int response, String message) {
|
||||
mResponse = response;
|
||||
if (message == null || message.trim().length() == 0) {
|
||||
mMessage = IabHelper.getResponseDesc(response);
|
||||
}
|
||||
else {
|
||||
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
|
||||
}
|
||||
}
|
||||
public int getResponse() { return mResponse; }
|
||||
public String getMessage() { return mMessage; }
|
||||
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
|
||||
public boolean isFailure() { return !isSuccess(); }
|
||||
public String toString() { return "IabResult: " + getMessage(); }
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a block of information about in-app items.
|
||||
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
|
||||
*/
|
||||
public class Inventory {
|
||||
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
|
||||
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
|
||||
|
||||
Inventory() { }
|
||||
|
||||
/** Returns the listing details for an in-app product. */
|
||||
public SkuDetails getSkuDetails(String sku) {
|
||||
return mSkuMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns purchase information for a given product, or null if there is no purchase. */
|
||||
public Purchase getPurchase(String sku) {
|
||||
return mPurchaseMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns whether or not there exists a purchase of the given product. */
|
||||
public boolean hasPurchase(String sku) {
|
||||
return mPurchaseMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/** Return whether or not details about the given product are available. */
|
||||
public boolean hasDetails(String sku) {
|
||||
return mSkuMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase a purchase (locally) from the inventory, given its product ID. This just
|
||||
* modifies the Inventory object locally and has no effect on the server! This is
|
||||
* useful when you have an existing Inventory object which you know to be up to date,
|
||||
* and you have just consumed an item successfully, which means that erasing its
|
||||
* purchase data from the Inventory you already have is quicker than querying for
|
||||
* a new Inventory.
|
||||
*/
|
||||
public void erasePurchase(String sku) {
|
||||
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs. */
|
||||
List<String> getAllOwnedSkus() {
|
||||
return new ArrayList<String>(mPurchaseMap.keySet());
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs of a given type */
|
||||
List<String> getAllOwnedSkus(String itemType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Purchase p : mPurchaseMap.values()) {
|
||||
if (p.getItemType().equals(itemType)) result.add(p.getSku());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns a list of all purchases. */
|
||||
List<Purchase> getAllPurchases() {
|
||||
return new ArrayList<Purchase>(mPurchaseMap.values());
|
||||
}
|
||||
|
||||
void addSkuDetails(SkuDetails d) {
|
||||
mSkuMap.put(d.getSku(), d);
|
||||
}
|
||||
|
||||
void addPurchase(Purchase p) {
|
||||
mPurchaseMap.put(p.getSku(), p);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app billing purchase.
|
||||
*/
|
||||
public class Purchase {
|
||||
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
|
||||
String mOrderId;
|
||||
String mPackageName;
|
||||
String mSku;
|
||||
long mPurchaseTime;
|
||||
int mPurchaseState;
|
||||
String mDeveloperPayload;
|
||||
String mToken;
|
||||
String mOriginalJson;
|
||||
String mSignature;
|
||||
|
||||
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mOriginalJson = jsonPurchaseInfo;
|
||||
JSONObject o = new JSONObject(mOriginalJson);
|
||||
mOrderId = o.optString("orderId");
|
||||
mPackageName = o.optString("packageName");
|
||||
mSku = o.optString("productId");
|
||||
mPurchaseTime = o.optLong("purchaseTime");
|
||||
mPurchaseState = o.optInt("purchaseState");
|
||||
mDeveloperPayload = o.optString("developerPayload");
|
||||
mToken = o.optString("token", o.optString("purchaseToken"));
|
||||
mSignature = signature;
|
||||
}
|
||||
|
||||
public String getItemType() { return mItemType; }
|
||||
public String getOrderId() { return mOrderId; }
|
||||
public String getPackageName() { return mPackageName; }
|
||||
public String getSku() { return mSku; }
|
||||
public long getPurchaseTime() { return mPurchaseTime; }
|
||||
public int getPurchaseState() { return mPurchaseState; }
|
||||
public String getDeveloperPayload() { return mDeveloperPayload; }
|
||||
public String getToken() { return mToken; }
|
||||
public String getOriginalJson() { return mOriginalJson; }
|
||||
public String getSignature() { return mSignature; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* Security-related methods. For a secure implementation, all of this code
|
||||
* should be implemented on a server that communicates with the
|
||||
* application on the device. For the sake of simplicity and clarity of this
|
||||
* example, this code is included here and is executed on the device. If you
|
||||
* must verify the purchases on the phone, you should obfuscate this code to
|
||||
* make it harder for an attacker to replace the code with stubs that treat all
|
||||
* purchases as verified.
|
||||
*/
|
||||
public class Security {
|
||||
private static final String TAG = "IABUtil/Security";
|
||||
|
||||
private static final String KEY_FACTORY_ALGORITHM = "RSA";
|
||||
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
|
||||
|
||||
/**
|
||||
* Verifies that the data was signed with the given signature, and returns
|
||||
* the verified purchase. The data is in JSON format and signed
|
||||
* with a private key. The data also contains the {@link PurchaseState}
|
||||
* and product ID of the purchase.
|
||||
* @param base64PublicKey the base64-encoded public key to use for verifying.
|
||||
* @param signedData the signed JSON string (signed, not encrypted)
|
||||
* @param signature the signature for the data, signed with the private key
|
||||
*/
|
||||
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
|
||||
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
|
||||
TextUtils.isEmpty(signature)) {
|
||||
Log.e(TAG, "Purchase verification failed: missing data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PublicKey key = Security.generatePublicKey(base64PublicKey);
|
||||
return Security.verify(key, signedData, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PublicKey instance from a string containing the
|
||||
* Base64-encoded public key.
|
||||
*
|
||||
* @param encodedPublicKey Base64-encoded public key
|
||||
* @throws IllegalArgumentException if encodedPublicKey is invalid
|
||||
*/
|
||||
public static PublicKey generatePublicKey(String encodedPublicKey) {
|
||||
try {
|
||||
byte[] decodedKey = Base64.decode(encodedPublicKey);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the signature from the server matches the computed
|
||||
* signature on the data. Returns true if the data is correctly signed.
|
||||
*
|
||||
* @param publicKey public key associated with the developer account
|
||||
* @param signedData signed data from server
|
||||
* @param signature server signature
|
||||
* @return true if the data and signature match
|
||||
*/
|
||||
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
|
||||
Signature sig;
|
||||
try {
|
||||
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(signedData.getBytes());
|
||||
if (!sig.verify(Base64.decode(signature))) {
|
||||
Log.e(TAG, "Signature verification failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "NoSuchAlgorithmException.");
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Signature exception.");
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.pierrox.lightning_launcher.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app product's listing details.
|
||||
*/
|
||||
public class SkuDetails {
|
||||
String mItemType;
|
||||
String mSku;
|
||||
String mType;
|
||||
String mPrice;
|
||||
String mTitle;
|
||||
String mDescription;
|
||||
String mJson;
|
||||
|
||||
public SkuDetails(String jsonSkuDetails) throws JSONException {
|
||||
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
|
||||
}
|
||||
|
||||
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mJson = jsonSkuDetails;
|
||||
JSONObject o = new JSONObject(mJson);
|
||||
mSku = o.optString("productId");
|
||||
mType = o.optString("type");
|
||||
mPrice = o.optString("price");
|
||||
mTitle = o.optString("title");
|
||||
mDescription = o.optString("description");
|
||||
}
|
||||
|
||||
public String getSku() { return mSku; }
|
||||
public String getType() { return mType; }
|
||||
public String getPrice() { return mPrice; }
|
||||
public String getTitle() { return mTitle; }
|
||||
public String getDescription() { return mDescription; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SkuDetails:" + mJson;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.4 KiB |
|
@ -1,34 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/add_item_margin">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="48dp"/>
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:singleLine="false"/>
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/full"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/full"
|
||||
android:layout_gravity="top|right"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -1,52 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="30dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/au_d"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/au_msg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/au_ok"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/ok"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/au_w"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="10dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/au_pw"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</FrameLayout>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="iab_unlock_free" translatable="false">unlock_free</string>
|
||||
<string name="iab_unlock_trial" translatable="false">unlock_trial</string>
|
||||
</resources>
|
|
@ -333,22 +333,6 @@ public abstract class LLApp extends Application {
|
|||
|
||||
public abstract Intent getWindowServiceIntent();
|
||||
|
||||
public abstract boolean isFreeVersion();
|
||||
|
||||
public abstract boolean isTrialVersion();
|
||||
|
||||
public abstract boolean isTrialVersionExpired();
|
||||
|
||||
public abstract long getTrialLeft();
|
||||
|
||||
public abstract void showFeatureLockedDialog(Context context);
|
||||
|
||||
public abstract void startUnlockProcess(Context context);
|
||||
|
||||
public abstract void installPromotionalIcons(Page dashboard);
|
||||
|
||||
public abstract void checkLicense();
|
||||
|
||||
public abstract boolean hasScriptEditor();
|
||||
|
||||
public abstract void startScriptEditor(int script_id, int line);
|
||||
|
|
|
@ -1461,7 +1461,7 @@ public abstract class Screen implements ItemLayout.ItemLayoutListener, ItemView.
|
|||
}
|
||||
|
||||
PageConfig c = page.config;
|
||||
if (NativeImage.isAvailable() && !LLApp.get().isFreeVersion() && mWallpaperView != null) {
|
||||
if (NativeImage.isAvailable() && mWallpaperView != null) {
|
||||
int alpha = Color.alpha(c.bgColor);
|
||||
if (alpha == 255) {
|
||||
mWallpaperView.setVisibility(View.GONE);
|
||||
|
|
|
@ -757,7 +757,7 @@ public class ScriptExecutor {
|
|||
}
|
||||
|
||||
public boolean canRunScriptGlobally() {
|
||||
return mEngine.canRunScripts() && !LLApp.get().isFreeVersion();
|
||||
return mEngine.canRunScripts();
|
||||
}
|
||||
|
||||
private boolean canRunScript(Script script) {
|
||||
|
|
|
@ -1032,11 +1032,6 @@
|
|||
<string name="tr_ty">Thank you for supporting Lightning!</string>
|
||||
<string name="tr_eu">Sorry, an error occurred…</string>
|
||||
<string name="tr_c">Canceled</string>
|
||||
<string name="tr_br_s">This feature is not available in the trial version</string>
|
||||
<string name="tr_rs_t">Unleash Lightning</string>
|
||||
<string name="tr_rs_s">Upgrade to get the most out of Lightning</string>
|
||||
<string name="tr_w">Thank you for testing Lightning!\n\nThe app will run in trial mode for 7 days: all features are enabled and can be used without restriction during this period. After the trial period, the app will still be usable but editing will not be enabled anymore.\n\nIf you like this app and wish to support it, please consider purchasing the key to unlock it.\n\nThank you!</string>
|
||||
<string name="tr_l">%d days left for the trial</string>
|
||||
<string name="tr_rm">Lightning need your feedback!\n\nWould you like to take a few seconds to rate this app on the Play Store?\n</string>
|
||||
<string name="ds_hls">This is a sample lock screen that you can customize at will.\nAdd apps or widgets then activate it through the lock screen settings (tap here).</string>
|
||||
<string name="ic">Image cropper</string>
|
||||
|
|
1
app/llx/wear/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,55 +0,0 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '26.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.pierrox.lightning_launcher.wear"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
flavorDimensions "std"
|
||||
|
||||
productFlavors {
|
||||
x86 {
|
||||
dimension "std"
|
||||
versionCode Integer.parseInt("8" + defaultConfig.versionCode)
|
||||
ndk {
|
||||
abiFilter "x86"
|
||||
}
|
||||
}
|
||||
armv7 {
|
||||
dimension "std"
|
||||
versionCode Integer.parseInt("2" + defaultConfig.versionCode)
|
||||
ndk {
|
||||
abiFilter "armeabi-v7a"
|
||||
}
|
||||
}
|
||||
arm {
|
||||
dimension "std"
|
||||
versionCode Integer.parseInt("1" + defaultConfig.versionCode)
|
||||
ndk {
|
||||
abiFilter "armeabi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'com.google.android.support:wearable:1.2.0'
|
||||
implementation 'com.google.android.wearable:wearable:1.0.0'
|
||||
implementation 'com.google.android.gms:play-services-wearable:6.5.87'
|
||||
implementation project(':core')
|
||||
}
|
17
app/llx/wear/proguard-rules.pro
vendored
|
@ -1,17 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/pierrot/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -1,37 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.pierrox.lightning_launcher.wear">
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:name="net.pierrox.lightning_launcher.wear.LLAppWear"
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<!-- android:icon="@mipmap/ic_launcher" -->
|
||||
<uses-library android:name="com.google.android.wearable" android:required="false" />
|
||||
|
||||
<activity
|
||||
android:name="net.pierrox.lightning_launcher.wear.activities.Starter"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.clockwork.home.preview"
|
||||
android:resource="@drawable/icon" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<!--<category android:name="android.intent.category.HOME" />-->
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name="net.pierrox.lightning_launcher.wear.overlay.WearWindowService"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,101 +0,0 @@
|
|||
package net.pierrox.lightning_launcher.wear;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import net.pierrox.lightning_launcher.LLApp;
|
||||
import net.pierrox.lightning_launcher.data.Page;
|
||||
import net.pierrox.lightning_launcher.wear.overlay.WearWindowService;
|
||||
|
||||
public class LLAppWear extends LLApp {
|
||||
|
||||
@Override
|
||||
public void displayPagerPage(int page, boolean reset_navigation_history) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getLockscreenServiceIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getWindowServiceIntent() {
|
||||
return new Intent(this, WearWindowService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreeVersion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrialVersionExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTrialLeft() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFeatureLockedDialog(Context context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUnlockProcess(Context context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installPromotionalIcons(Page dashboard) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkLicense() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScriptEditor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScriptEditor(int script_id, int line) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockScreenLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockLockScreen(boolean restore_previous_task) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getActivityClassForScriptExecutionTarget(int target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart(boolean relaunch) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLightningIntent(Intent intent) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package net.pierrox.lightning_launcher.wear.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.pierrox.lightning_launcher.LLApp;
|
||||
import net.pierrox.lightning_launcher.overlay.WindowService;
|
||||
|
||||
public class Starter extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
startService(LLApp.get().getWindowServiceIntent());
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package net.pierrox.lightning_launcher.wear.overlay;
|
||||
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.wearable.Wearable;
|
||||
|
||||
import net.pierrox.lightning_launcher.overlay.WindowService;
|
||||
|
||||
public class WearWindowService extends WindowService {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
final DisplayManager dm = (DisplayManager) getSystemService(DISPLAY_SERVICE);
|
||||
dm.registerDisplayListener(new DisplayManager.DisplayListener() {
|
||||
@Override
|
||||
public void onDisplayAdded(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayRemoved(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int i) {
|
||||
int state = dm.getDisplay(i).getState();
|
||||
if(state == Display.STATE_DOZE || state == Display.STATE_OFF) {
|
||||
hideWorkspace();
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
setupDataLayer();
|
||||
}
|
||||
|
||||
private void setupDataLayer() {
|
||||
GoogleApiClient mGoogleAppiClient = new GoogleApiClient.Builder(this)
|
||||
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
|
||||
@Override
|
||||
public void onConnected(Bundle connectionHint) {
|
||||
Log.e("XXX", "onConnected: " + connectionHint);
|
||||
// Now you can use the data layer API
|
||||
}
|
||||
@Override
|
||||
public void onConnectionSuspended(int cause) {
|
||||
Log.e("XXX", "onConnectionSuspended: " + cause);
|
||||
}
|
||||
})
|
||||
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
|
||||
@Override
|
||||
public void onConnectionFailed(ConnectionResult result) {
|
||||
Log.e("XXX", "onConnectionFailed: " + result);
|
||||
}
|
||||
})
|
||||
.addApi(Wearable.API)
|
||||
.build();
|
||||
|
||||
mGoogleAppiClient.connect();
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 11 KiB |
|
@ -1,3 +0,0 @@
|
|||
<resources>
|
||||
<string name="app_name">Wear</string>
|
||||
</resources>
|
|
@ -1,158 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":wear" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="llx-v14" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":wear" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="armv7Debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleArmv7Debug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileArmv7DebugSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateArmv7DebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/armv7/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/armv7/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/armv7/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/armv7/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/armv7/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/armv7/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/armv7/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/armv7/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/armv7/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7Debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/armv7/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/armv7/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7Debug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/armv7/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7Debug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/armv7/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestArmv7/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testArmv7/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="com.android.support:recyclerview-v7-22.0.0" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-base-6.5.87" level="project" />
|
||||
<orderEntry type="library" name="__local_aars__:/home/pierrot/Documents/Devel/Android/Projects/LightningLauncher/projects/llx/core/libs/dx.jar:unspecified@jar" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.wearable:wearable:1.0.0@jar" level="project" />
|
||||
<orderEntry type="library" name="com.android.support:support-v4-22.0.0" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-wearable-6.5.87" level="project" />
|
||||
<orderEntry type="library" name="com.android.support:support-annotations:22.0.0@jar" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.support:wearable-1.2.0" level="project" />
|
||||
<orderEntry type="module" module-name="core" />
|
||||
</component>
|
||||
</module>
|
1
app/llx/wear_manager/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,27 +0,0 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '26.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.pierrox.lightning_launcher.wear_manager"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
wearApp project(':wear')
|
||||
compile 'com.google.android.gms:play-services-wearable:+'
|
||||
}
|
17
app/llx/wear_manager/proguard-rules.pro
vendored
|
@ -1,17 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/pierrot/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -1,13 +0,0 @@
|
|||
package net.pierrox.lightning_launcher.wear_manager;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.pierrox.lightning_launcher.wear_manager">
|
||||
|
||||
<application android:allowBackup="true" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 7.5 KiB |
|
@ -1,3 +0,0 @@
|
|||
<resources>
|
||||
<string name="app_name">Wear Manager</string>
|
||||
</resources>
|
|
@ -1,5 +0,0 @@
|
|||
<resources>
|
||||
|
||||
|
||||
|
||||
</resources>
|
|
@ -1,113 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":wear_manager" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="llx-v14" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":wear_manager" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-wearable-9.4.0" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-tasks-9.4.0" level="project" />
|
||||
<orderEntry type="library" name="com.android.support:support-annotations:23.0.0@jar" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-basement-9.4.0" level="project" />
|
||||
<orderEntry type="library" name="com.android.support:support-v4-23.0.0" level="project" />
|
||||
<orderEntry type="library" name="com.google.android.gms:play-services-base-9.4.0" level="project" />
|
||||
</component>
|
||||
</module>
|