Download File
Download Project
Settings
Line Wrap
Themes
default
ambiance
bespin
dracula
eclipse
material
mbo
mdn-like
neat
solarized dark
ttcn
zenburn
BaseGamePlayActivity.java
/* * Copyright 2017 Google Inc. All Rights Reserved. * * 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.example.billingmodule; import static com.android.billingclient.api.BillingClient.BillingResponse; import static com.example.billingmodule.billing.BillingManager.BILLING_MANAGER_NOT_INITIALIZED; import android.app.AlertDialog; import android.os.Bundle; import android.os.Looper; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.example.billingmodule.billing.BillingManager; import com.example.billingmodule.billing.BillingProvider; import com.example.billingmodule.skulist.AcquireFragment; /** * Example game using in-app billing version 3. * * Before attempting to run this sample, please read the README file. It * contains important information on how to set up this project. * * All the game-specific logic is implemented here in BaseGamePlayActivity, while billing-specific * logic is moved into billing package. Don't forget to add a billing-library module which * it depends on. * * This game is a simple "driving" game where the player can buy gas * and drive. The car has a tank which stores gas. When the player purchases * gas, the tank fills up (1/4 tank at a time). When the player drives, the gas * in the tank diminishes (also 1/4 tank at a time). * * The user can also purchase a "premium upgrade" that gives them a red car * instead of the standard blue one (exciting!). * * The user can also purchase a subscription ("infinite gas") that allows them * to drive without using up any gas while that subscription is active. * * It's important to note the consumption mechanics for each item. * * PREMIUM: the item is purchased and NEVER consumed. So, after the original * purchase, the player will always own that item. The application knows to * display the red car instead of the blue one because it queries whether * the premium "item" is owned or not. * * INFINITE GAS: this is a subscription, and subscriptions can't be consumed. * * GAS: when gas is purchased, the "gas" item is then owned. We consume it * when we apply that item's effects to our app's world, which to us means * filling up 1/4 of the tank. This happens immediately after purchase! * It's at this point (and not when the user drives) that the "gas" * item is CONSUMED. Consumption should always happen when your game * world was safely updated to apply the effect of the purchase. So, * in an example scenario: * * BEFORE: tank at 1/2 * ON PURCHASE: tank at 1/2, "gas" item is owned * IMMEDIATELY: "gas" is consumed, tank goes to 3/4 * AFTER: tank at 3/4, "gas" item NOT owned any more * * Another important point to notice is that it may so happen that * the application crashed (or anything else happened) after the user * purchased the "gas" item, but before it was consumed. That's why, * on startup, we check if we own the "gas" item, and, if so, * we have to apply its effects to our world and consume it. This * is also very important! */ public abstract class BaseGamePlayActivity extends FragmentActivity implements BillingProvider { // Debug tag, for logging private static final String TAG = "BaseGamePlayActivity"; // Tag for a dialog that allows us to find it when screen was rotated private static final String DIALOG_TAG = "dialog"; // Default sample's package name to check if you changed it private static final String DEFAULT_PACKAGE_PREFIX = "com.example"; private BillingManager mBillingManager; private AcquireFragment mAcquireFragment; private MainViewController mViewController; private View mScreenWait, mScreenMain; private ImageView mCarImageView, mGasImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutResId()); // Start the controller and load game data mViewController = new MainViewController(this); if (getPackageName().startsWith(DEFAULT_PACKAGE_PREFIX)) { throw new RuntimeException("Please change the sample's package name!"); } // Try to restore dialog fragment if we were showing it prior to screen rotation if (savedInstanceState != null) { mAcquireFragment = (AcquireFragment) getSupportFragmentManager() .findFragmentByTag(DIALOG_TAG); } // Create and initialize BillingManager which talks to BillingLibrary mBillingManager = new BillingManager(this, mViewController.getUpdateListener()); mScreenWait = findViewById(R.id.screen_wait); mScreenMain = findViewById(R.id.screen_main); mCarImageView = ((ImageView) findViewById(R.id.free_or_premium)); mGasImageView = ((ImageView) findViewById(R.id.gas_gauge)); // Specify purchase and drive buttons listeners // Note: This couldn't be done inside *.xml for Android TV since TV layout is inflated // via AppCompat findViewById(R.id.button_purchase).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onPurchaseButtonClicked(view); } }); findViewById(R.id.button_drive).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onDriveButtonClicked(view); } }); } @Override protected void onResume() { super.onResume(); // Note: We query purchases in onResume() to handle purchases completed while the activity // is inactive. For example, this can happen if the activity is destroyed during the // purchase flow. This ensures that when the activity is resumed it reflects the user's // current purchases. if (mBillingManager != null && mBillingManager.getBillingClientResponseCode() == BillingResponse.OK) { mBillingManager.queryPurchases(); } } @Override public BillingManager getBillingManager() { return mBillingManager; } @Override public boolean isPremiumPurchased() { return mViewController.isPremiumPurchased(); } @Override public boolean isGoldMonthlySubscribed() { return mViewController.isGoldMonthlySubscribed(); } @Override public boolean isGoldYearlySubscribed() { return mViewController.isGoldYearlySubscribed(); } @Override public boolean isTankFull() { return mViewController.isTankFull(); } protected abstract int getLayoutResId(); /** * User clicked the "Buy Gas" button - show a purchase dialog with all available SKUs */ public void onPurchaseButtonClicked(final View arg0) { Log.d(TAG, "Purchase button clicked."); if (mAcquireFragment == null) { mAcquireFragment = new AcquireFragment(); } if (!isAcquireFragmentShown()) { mAcquireFragment.show(getSupportFragmentManager(), DIALOG_TAG); if (mBillingManager != null && mBillingManager.getBillingClientResponseCode() > BILLING_MANAGER_NOT_INITIALIZED) { mAcquireFragment.onManagerReady(this); } } } /** * Drive button clicked. Burn gas! */ public void onDriveButtonClicked(View arg0) { Log.d(TAG, "Drive button clicked."); if (mViewController.isTankEmpty()) { alert(R.string.alert_no_gas); } else { mViewController.useGas(); alert(R.string.alert_drove); updateUi(); } } @Override public void onDestroy() { Log.d(TAG, "Destroying helper."); if (mBillingManager != null) { mBillingManager.destroy(); } super.onDestroy(); } /** * Remove loading spinner and refresh the UI */ public void showRefreshedUi() { setWaitScreen(false); updateUi(); if (mAcquireFragment != null) { mAcquireFragment.refreshUI(); } } /** * Show an alert dialog to the user * @param messageId String id to display inside the alert dialog */ @UiThread void alert(@StringRes int messageId) { alert(messageId, null); } /** * Show an alert dialog to the user * @param messageId String id to display inside the alert dialog * @param optionalParam Optional attribute for the string */ @UiThread void alert(@StringRes int messageId, @Nullable Object optionalParam) { if (Looper.getMainLooper().getThread() != Thread.currentThread()) { throw new RuntimeException("Dialog could be shown only from the main thread"); } AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setNeutralButton("OK", null); if (optionalParam == null) { bld.setMessage(messageId); } else { bld.setMessage(getResources().getString(messageId, optionalParam)); } bld.create().show(); } void onBillingManagerSetupFinished() { if (mAcquireFragment != null) { mAcquireFragment.onManagerReady(this); } } @VisibleForTesting public MainViewController getViewController() { return mViewController; } /** * Enables or disables the "please wait" screen. */ private void setWaitScreen(boolean set) { mScreenMain.setVisibility(set ? View.GONE : View.VISIBLE); mScreenWait.setVisibility(set ? View.VISIBLE : View.GONE); } /** * Sets image resource and also adds a tag to be able to verify that image is correct in tests */ private void setImageResourceWithTestTag(ImageView imageView, @DrawableRes int resId) { imageView.setImageResource(resId); imageView.setTag(resId); } /** * Update UI to reflect model */ @UiThread private void updateUi() { Log.d(TAG, "Updating the UI. Thread: " + Thread.currentThread().getName()); // Update car's color to reflect premium status or lack thereof setImageResourceWithTestTag(mCarImageView, isPremiumPurchased() ? R.drawable.premium : R.drawable.free); // Update gas gauge to reflect tank status setImageResourceWithTestTag(mGasImageView, mViewController.getTankResId()); if (isGoldMonthlySubscribed() || isGoldYearlySubscribed()) { mCarImageView.setBackgroundColor(ContextCompat.getColor(this, R.color.gold)); } } public boolean isAcquireFragmentShown() { return mAcquireFragment != null && mAcquireFragment.isVisible(); } public DialogFragment getDialogFragment() { return mAcquireFragment; } }