package com.ctf.games.release; import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG; import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL; import static org.cocos2dx.lib.Cocos2dxHelper.getActivity; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.Window; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; import com.bytedance.sdk.open.tiktok.TikTokOpenApiFactory; import com.bytedance.sdk.open.tiktok.api.TikTokOpenApi; import com.bytedance.sdk.open.tiktok.authorize.model.Authorization; import com.bytedance.sdk.open.tiktok.base.MediaContent; import com.bytedance.sdk.open.tiktok.base.VideoObject; import com.bytedance.sdk.open.tiktok.share.Share; import com.ctf.games.release.activity.WebPageActivity; import com.ctf.games.release.appauth.AppAuthSvr; import com.ctf.games.release.apple.AppleLoginActivity; import com.ctf.games.release.ui.UIManager; import com.ctf.games.release.wallet.WalletUtil; import com.facebook.AccessToken; import com.facebook.CallbackManager; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.appevents.AppEventsLogger; import com.facebook.login.LoginManager; import com.facebook.login.LoginResult; import com.facebook.share.model.ShareLinkContent; import com.facebook.share.widget.ShareDialog; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.Scopes; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.Scope; import com.google.android.gms.tasks.Task; import com.google.api.services.drive.DriveScopes; import com.google.firebase.analytics.FirebaseAnalytics; import com.jc.jcfw.JcSDK; import com.jc.jcfw.google.PayClient; import com.jc.jcfw.util.JsonUtils; import com.unity3d.player.UnityPlayerActivity; import net.openid.appauth.AuthState; import org.cocos2dx.lib.Cocos2dxHelper; import org.cocos2dx.lib.CocosJSHelper; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends UnityPlayerActivity implements Cocos2dxHelper.Cocos2dxHelperListener { private static final String TAG = MainActivity.class.getSimpleName(); public static MainActivity app; // AppAuth private static final int RC_AUTH = 0X04; // google sign private static final int RC_SIGN_IN = 0X05; // code for request drive to upload private static final int RC_REQUEST_DRIVE_TO_UPLOAD = 0X051; // code for request drive to download private static final int RC_REQUEST_DRIVE_TO_READ = 0X052; public static final int FILE_SELECTOR_CODE = 0X014; private String title; private String mFunID; // AppAuth private AppAuthSvr mAppAuthSvr; private ExecutorService mExecutor; private TikTokOpenApi tiktokOpenApi; public String getFunId() { return mFunID; } private GoogleSignInClient mGoogleSignInClient; // facebook login private CallbackManager mCallbackManager; private FirebaseAnalytics mFirebaseAnalytics; private AppEventsLogger fbLogger; private WalletUtil mWalletUtil; public boolean isGooglePlayServicesAvailable() { return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS; } @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); Log.i(TAG, "onCreate: " + getIntent().getDataString()); // Obtain the FirebaseAnalytics instance. mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); onLoadNativeLibraries(); app = this; Cocos2dxHelper.init(this); CocosJSHelper.initJSEnv(getApplicationContext()); // begin of google sign GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestScopes(new Scope(Scopes.EMAIL)) .requestIdToken(getString(R.string.default_web_client_id1)) .build(); mGoogleSignInClient = GoogleSignIn.getClient(this, gso); // end of google sign // begin of google oauth sign mAppAuthSvr = new AppAuthSvr(this); mExecutor = Executors.newSingleThreadExecutor(); mAppAuthSvr.init(mExecutor); // end of google oauth sign // begin of facebook login initFacebookSDK(); // end of facebook login tiktokOpenApi = TikTokOpenApiFactory.create(this); PayClient payClient = PayClient.getInstance(); payClient.init(this); mWalletUtil = new WalletUtil(this); // JSTimers timer = new JSTimers(Looper.getMainLooper()); // timer.sendEmptyMessage(JSTimers.JS_TICK); } @Override protected void onResume(){ super.onResume(); String[] paramArr = new String[0]; JcSDK.callNativeJS("", "onGameResume", paramArr); } public static class JSTimers extends Handler { public static final int JS_TICK = 0; public JSTimers(@NonNull Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (msg.what == JS_TICK) { JcSDK.tick(0.033f); sendEmptyMessageDelayed(JS_TICK, 33); } else { removeMessages(JS_TICK); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); mCallbackManager.onActivityResult(requestCode, resultCode, data); UIManager.getSingleton().onActivityResult(this, requestCode, resultCode, data); if (resultCode == RESULT_OK && data != null) { switch (requestCode) { case RC_AUTH: mAppAuthSvr.parseLoginResult(data); break; case RC_SIGN_IN: handleSignInResult(GoogleSignIn.getSignedInAccountFromIntent(data)); break; case FILE_SELECTOR_CODE: shareToTikTok(mFunID, data.getData()); break; case RC_REQUEST_DRIVE_TO_UPLOAD: mExecutor.submit(() -> mWalletUtil.uploadCfgWithGPS(_result -> {})); break; case RC_REQUEST_DRIVE_TO_READ: mExecutor.submit(() -> mWalletUtil.downloadCfgWithGPS(_file -> mWalletUtil.getPassLocal())); break; } } else { if (requestCode == RC_REQUEST_DRIVE_TO_UPLOAD || requestCode == RC_REQUEST_DRIVE_TO_READ) { mWalletUtil.errorCB("activity result with code: " + resultCode); } if (requestCode == RC_SIGN_IN) { Task task = GoogleSignIn.getSignedInAccountFromIntent(data); handleSignInResult(task); } mFunID = ""; } } @Override protected void onStart() { super.onStart(); UIManager.getSingleton().onStartActivity(this); if (mExecutor.isShutdown()) { mExecutor = Executors.newSingleThreadExecutor(); } } // begin for unity protected void onLoadNativeLibraries() { try { ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; String libName = bundle.getString("android.app.lib_name"); System.loadLibrary(libName); } catch (Throwable e) { e.printStackTrace(); } } @Override public void showDialog(String pTitle, String pMessage) { } @Override public void runOnGLThread(Runnable pRunnable) { } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // Forward results to EasyPermissions UIManager.getSingleton().onRequestPermissionsResult(requestCode, permissions, grantResults); } public void signWithGoogle(String funId) { this.mFunID = funId; if (isGooglePlayServicesAvailable()) { GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this); if (account != null) { Log.w(TAG, "already login: " + account.getIdToken()); mGoogleSignInClient.silentSignIn() .addOnCompleteListener(this, this::handleSignInResult); } else { Intent signInIntent = mGoogleSignInClient.getSignInIntent(); startActivityForResult(signInIntent, RC_SIGN_IN); } } else { Log.d(TAG, "no gms, use app auth"); AuthState state = mAppAuthSvr.getCurrentState(); if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.w(TAG, "need refresh accessToken"); mAppAuthSvr.refreshToken(funId, null); } else { Log.w(TAG, "already login, accessToken not expired, id token:: " + state.getIdToken()); JcSDK.nativeCb(this.mFunID, null, state.getIdToken()); } } else { Log.w(TAG, "not login"); mExecutor.submit(() -> mAppAuthSvr.doAuth(funId, null)); } } } public void signOutGoogle(String funId) { mGoogleSignInClient.signOut() .addOnCompleteListener(this, task -> { // ... }); } private void handleSignInResult(Task completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); Log.w(TAG, "signIn success: "); Log.w(TAG, "gsa idToken: " + account.getIdToken()); JcSDK.nativeCb(this.mFunID, null, account.getIdToken()); // Signed in successfully, show authenticated UI. } catch (ApiException e) { // The ApiException status code indicates the detailed failure reason. // Please refer to the GoogleSignInStatusCodes class reference for more // information. Log.w(TAG, "signInResult:failed code=" + e.getStatusCode()); JcSDK.nativeCb(this.mFunID, "signInResult:failed code=" + e.getStatusCode(), null); } } // sign with tiktok public void signWithTiktok(String funId) { this.mFunID = funId; Log.i(TAG, "login with tiktok: " + funId); // STEP 1: Create an instance of TiktokOpenApi // STEP 2: Create an instance of Authorization.Request and set parameters Authorization.Request request = new Authorization.Request(); request.scope = "user.info.basic,video.list"; request.state = funId; tiktokOpenApi.authorize(request); } // begin of facebook login private void initFacebookSDK() { mCallbackManager = CallbackManager.Factory.create(); fbLogger = AppEventsLogger.newLogger(this); LoginManager.getInstance().registerCallback(mCallbackManager, new FacebookCallback() { @Override public void onSuccess(LoginResult loginResult) { AccessToken accessToken = AccessToken.getCurrentAccessToken(); Log.d(TAG, "Login Success:: accessToken: " + accessToken.getToken()); if (!verifyFbAccessToken(accessToken)) { JcSDK.nativeCb(MainActivity.app.mFunID, "access token expired", null); } } @Override public void onCancel() { Log.d(TAG, "Login cancel"); JcSDK.nativeCb(MainActivity.app.mFunID, "user login cancel", null); } @Override public void onError(FacebookException exception) { Log.i(TAG, "Login error: " + exception.getMessage()); JcSDK.nativeCb(MainActivity.app.mFunID, exception.getMessage(), null); } }); } public void signWithFacebook(String funId) { Log.i(TAG, "login with facebook: " + funId); this.mFunID = funId; AccessToken accessToken = AccessToken.getCurrentAccessToken(); if (!verifyFbAccessToken(accessToken)) { LoginManager.getInstance().logInWithReadPermissions(this, Arrays.asList("public_profile", "email")); } } public void shareWithFacebook(String content) { ShareLinkContent linkContent = new ShareLinkContent.Builder() .setContentUrl(Uri.parse("https://www.google.com")) .setQuote(content) .build(); ShareDialog.show(this, linkContent); } private boolean verifyFbAccessToken(AccessToken accessToken) { boolean isLoggedIn = accessToken != null && !accessToken.isExpired(); if (isLoggedIn) { JcSDK.nativeCb(this.mFunID, null, accessToken.getToken()); return true; } else { return false; } } // end of facebook login public void signWithTwitter(String funId) { Log.i(TAG, "login with twitter: " + funId); this.mFunID = funId; } public void logEvent(String content) { try { Bundle bundle = JsonUtils.convertJsonToBundle(content); String eventName = bundle.getString("name", "custom_event"); mFirebaseAnalytics.logEvent(eventName, bundle); fbLogger.logEvent(eventName, bundle); } catch (JSONException e) { Log.e(TAG, "log event JSONException: " + e.getMessage()); } catch (Exception e) { Log.e(TAG, "log event Exception: " + e.getMessage()); } } public void signWithApple(String funId) { this.mFunID = funId; Log.i(TAG, "login with apple: " + funId); runOnUiThread(() -> { Intent intent = new Intent(this, AppleLoginActivity.class); intent.putExtra("funId", funId); startActivity(intent); }); } public void showPage(String fid, final String url) { runOnUiThread(() -> { Intent intent = new Intent(this, WebPageActivity.class); intent.putExtra("url", url); startActivity(intent); }); } public void shareToTikTok(String funId, Uri uriToImage) { this.mFunID = funId; grantUriPermission("com.zhiliaoapp.musically", uriToImage, Intent.FLAG_GRANT_READ_URI_PERMISSION); grantUriPermission("com.ss.android.ugc.trill", uriToImage, Intent.FLAG_GRANT_READ_URI_PERMISSION); Log.i(TAG, "share to tiktok: " + uriToImage.toString()); if (tiktokOpenApi.isShareSupportFileProvider()) { Share.Request request = new Share.Request(); ArrayList mUri = new ArrayList<>(); mUri.add(uriToImage.toString()); VideoObject videoObject = new VideoObject(); videoObject.mVideoPaths = mUri; MediaContent content = new MediaContent(); content.mMediaObject = videoObject; // 3.set required parameters request.mMediaContent = content; request.mState = funId; request.mShareFormat = Share.Format.DEFAULT; tiktokOpenApi.share(request); } // share with Android ShareSheet // runOnUiThread(() -> { // Log.i(TAG, "share to tiktok: " + uriToImage); // Intent shareIntent = new Intent(); // shareIntent.setAction(Intent.ACTION_SEND); // shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage); // shareIntent.setType("video/*"); // startActivity(Intent.createChooser(shareIntent, "share")); // }); } private void openFileSelector() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("video/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, FILE_SELECTOR_CODE); } public void passStorageState(String funID, String account) { Log.i(TAG, "passStorageState with: " + account); int touchIDState = BiometricManager.from(this) .canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS ? 1 : 0; JSONObject result = new JSONObject(); try { result.put("biometrics", touchIDState); JcSDK.nativeCb(funID, null, result.toString()); } catch (JSONException e) { JcSDK.nativeCb(funID, "JSON error", null); } } private void loginAndRequestDrivePermission(int requestCode) { Log.i(TAG, "no drive permission"); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(getString(R.string.default_web_client_id1)) .requestScopes(new Scope(Scopes.EMAIL), new Scope(DriveScopes.DRIVE_APPDATA)) .build(); mGoogleSignInClient = GoogleSignIn.getClient(this, gso); Intent signInIntent = mGoogleSignInClient.getSignInIntent(); startActivityForResult(signInIntent, requestCode); } public void storagePass(String funID, String account, String password) { Log.i(TAG, String.format("storagePass with: %s | %s", funID, account)); this.mFunID = funID; mWalletUtil.updateParams(funID, account); mWalletUtil.savePassToLocal(password, _file -> { if (isGooglePlayServicesAvailable()) { if (!GoogleSignIn.hasPermissions( GoogleSignIn.getLastSignedInAccount(getActivity()), new Scope(DriveScopes.DRIVE_APPDATA))) { loginAndRequestDrivePermission(RC_REQUEST_DRIVE_TO_UPLOAD); } else { Log.i(TAG, "had drive permission"); mWalletUtil.uploadCfgWithGPS(_result -> {}); } } else { AuthState state = mAppAuthSvr.getCurrentState(); if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.d(TAG, "need refresh accessToken"); mAppAuthSvr.refreshToken(funID, _result -> mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{})); } else { mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); } } else { Log.w(TAG, "not login"); mExecutor.submit(() -> mAppAuthSvr.doAuth(funID, _result -> mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}))); } } }); // runOnUiThread(() -> { // Intent intent = new Intent(this, BiometricActivity.class); // intent.putExtra("action", "encrypt"); // intent.putExtra("funID", funId); // intent.putExtra("account", account); // intent.putExtra("password", password); // startActivity(intent); // }); } public void authGetStoragePass(String funID, String account) { Log.i(TAG, "authGetStoragePass with: " + account); this.mFunID = funID; mWalletUtil.updateParams(funID, account); if (isGooglePlayServicesAvailable()) { if (!mWalletUtil.localCfgExists(account)) { if (!GoogleSignIn.hasPermissions( GoogleSignIn.getLastSignedInAccount(getActivity()), new Scope(DriveScopes.DRIVE_APPDATA))) { loginAndRequestDrivePermission(RC_REQUEST_DRIVE_TO_READ); } else { mWalletUtil.downloadCfgWithGPS(_file -> mWalletUtil.getPassLocal()); } } else { mWalletUtil.getPassLocal(); } } else { if (!mWalletUtil.localCfgExists(account)) { AuthState state = mAppAuthSvr.getCurrentState(); // 1. check whether the google account has been logged in, this situation occurs when non-google login // 2. check if already had permission of drive appdata, add permission of drive appdata to auth_config // 3. check if need refresh access token if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.d(TAG, "need refresh accessToken"); mAppAuthSvr.refreshToken(funID, _result -> mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> mWalletUtil.getPassLocal())); } else { Log.d(TAG, "access token no need refresh"); mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> mWalletUtil.getPassLocal()); } } else { Log.w(TAG, "not login"); mExecutor.submit(() -> mAppAuthSvr.doAuth(funID, _result -> mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> mWalletUtil.getPassLocal()))); } } else { mWalletUtil.getPassLocal(); } } // runOnUiThread(() -> { // Intent intent = new Intent(this, BiometricActivity.class); // intent.putExtra("action", "decrypt"); // intent.putExtra("funID", funId); // intent.putExtra("account", account); // startActivity(intent); // }); } public void getClientId(String funID) { Log.i(TAG, "getClientId "); } }