Android Google 日历 API 推送到 Google 播放时无法正常工作

Android Google Calendar API not working when pushed to Google Play

我在使用 google 日历 api 和 android 时遇到问题。一切正常(调试 + 发布)但是当将发布 apk 推送到 Play 商店时,下载的版本在尝试使用 api 时抛出异常。我正在记录产生的错误:println("couldn't insert into Google Calendar! " + e.message) 这是 I/System.out: couldn't insert into Google Calendar! null

这是 kotlin 的东西还是我遗漏了什么?

这是我的 GoogleCalendar.kt:

package de.schooldiary.flokol120.schooldiary.utils

import android.Manifest
import android.app.Activity
import android.content.Context
import android.net.ConnectivityManager
import android.os.AsyncTask
import android.widget.Toast
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.api.client.extensions.android.http.AndroidHttp
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.client.util.DateTime
import com.google.api.client.util.ExponentialBackOff
import com.google.api.services.calendar.CalendarScopes
import com.google.api.services.calendar.model.Event
import com.google.api.services.calendar.model.EventDateTime
import com.google.api.services.calendar.model.EventReminder
import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.EasyPermissions
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList

class GoogleCalendar(val context: Activity): EasyPermissions.PermissionCallbacks {

    var mCredential: GoogleAccountCredential
    companion object {
        internal const val REQUEST_ACCOUNT_PICKER: Int = 1000
        internal const val REQUEST_AUTHORIZATION = 1001
        internal const val REQUEST_GOOGLE_PLAY_SERVICES = 1002
        internal const val REQUEST_PERMISSION_GET_ACCOUNTS = 1003
        internal const val PREF_ACCOUNT_NAME = "accountName"

        var lastAdded: ArrayList<Any> = ArrayList()
        var lastError: String = ""

        private class InsertData internal constructor(credential: GoogleAccountCredential): AsyncTask<ArrayList<Any>, Void, Void>(){
            private var mService: com.google.api.services.calendar.Calendar? = null

            init {
                val transport = AndroidHttp.newCompatibleTransport()
                val jsonFactory = JacksonFactory.getDefaultInstance()
                mService = com.google.api.services.calendar.Calendar.Builder(
                        transport, jsonFactory, credential)
                        .setApplicationName("School Diary")
                        .build()
            }
            override fun doInBackground(vararg params: ArrayList<Any>?): Void? {
                if(params.isNotEmpty()){
                    for (subList: ArrayList<Any>? in params){
                        if(subList != null) {
                            insertEvent(subList[0] as String, subList[1] as String, subList[2] as String,
                                    subList[3] as String, subList[4] as Boolean, subList[5] as Boolean,
                                    subList[6] as Boolean, subList[7] as Activity)
                        }
                    }
                }
                return null
            }

            @Throws(IOException::class)
            private fun insertEvent(summary: String, des: String, startDate: String, endDate: String,
                                    repeating: Boolean, reminder: Boolean, dateTime: Boolean, context: Activity) {
                try{
                    if(!startDate.isEmpty()){
                        val startDater: DateTime
                        val endDater: DateTime
                        val formatter: SimpleDateFormat
                        println(startDate)
                        println(endDate)
                        if(!dateTime){
                            formatter = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
                            startDater = DateTime(formatter.parse(startDate))
                            endDater = DateTime(formatter.parse(endDate))
                        }else{
                            formatter = SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS", Locale.getDefault())
                            startDater = DateTime(formatter.parse(startDate))
                            endDater = DateTime(formatter.parse(endDate))
                        }
                        val event = Event()
                                .setSummary(summary)
                                .setDescription(des)
                        val start = EventDateTime()
                                .setDateTime(startDater)
                                .setTimeZone("Europe/Berlin")
                        event.start = start
                        val end = EventDateTime()
                                .setDateTime(endDater)
                                .setTimeZone("Europe/Berlin")
                        event.end = end
                        if(repeating){
                            val recurrence = arrayOf("RRULE:FREQ=WEEKLY")
                            event.recurrence = Arrays.asList(*recurrence)
                        }
                        if(reminder){
                            val reminderOverrides = arrayOf(EventReminder().setMethod("email").setMinutes(24 * 60), EventReminder().setMethod("popup").setMinutes(10))
                            val reminders = Event.Reminders()
                                    .setUseDefault(false)
                                    .setOverrides(Arrays.asList(*reminderOverrides))
                            event.reminders = reminders
                        }else{
                            event.reminders = Event.Reminders()
                                    .setUseDefault(false)
                        }
                        val calendarId = "primary"
                        //val calendarId = context.getString(R.string.app_name)
                        mService?.events()?.insert(calendarId, event)?.setSendNotifications(true)?.execute()
                        lastError = "added event successfully to google calendar!!"
                    }
                }catch (e: UserRecoverableAuthIOException){
                    lastAdded = arrayListOf(summary, des, startDate, endDate, repeating, reminder, dateTime)
                    //Toast.makeText(context, "something went wrong, adding the event to your google calendar (${e.message})", Toast.LENGTH_LONG).show()
                    context.startActivityForResult(e.intent, REQUEST_AUTHORIZATION)
                    lastError = "something went wrong, adding the event to your google calendar (${e.message})"
                }catch (e: Exception){
                    //Toast.makeText(context, "couldn't insert into Google Calendar! " + e.localizedMessage, Toast.LENGTH_LONG).show()
                    println("couldn't insert into Google Calendar! " + e.message)
                    lastError = "something went wrong, adding the event to your google calendar (${e.message})"
                    //Toast.makeText(context, "something went wrong, adding the event to your google calendar (${e.message})", Toast.LENGTH_LONG).show()
                }
            }
        }


    }
    val SCOPES = arrayOf(CalendarScopes.CALENDAR)

    init {
        // Initialize credentials and service object.
        mCredential = GoogleAccountCredential.usingOAuth2(
                context.applicationContext, Arrays.asList(*SCOPES))
                .setBackOff(ExponentialBackOff())
    }

    /**
     * Attempt to call the API, after verifying that all the preconditions are
     * satisfied. The preconditions are: Google Play Services installed, an
     * account was selected and the device currently has online access. If any
     * of the preconditions are not satisfied, the app will prompt the user as
     * appropriate.
     */
    fun insertEvent(summary: String, des: String, startDate: String, endDate: String,
                    repeating: Boolean, reminder: Boolean, dateTime: Boolean) {
        if(lastError != ""){
            Toast.makeText(context, lastError, Toast.LENGTH_LONG).show()
        }
        if (!isGooglePlayServicesAvailable()) {
            acquireGooglePlayServices()
        } else if (mCredential.selectedAccountName == null) {
            chooseAccount(summary, des, startDate, endDate, repeating, reminder, dateTime)
        } else if (!isDeviceOnline()) {
            Toast.makeText(context, "No network connection available.", Toast.LENGTH_LONG).show()
        } else {
            //MakeRequestTask(mCredential).execute()
            InsertData(mCredential).execute(arrayListOf(summary, des, startDate, endDate, repeating, reminder, dateTime, context))
        }
    }

    /**
     * Attempts to set the account used with the API credentials. If an account
     * name was previously saved it will use that one; otherwise an account
     * picker dialog will be shown to the user. Note that the setting the
     * account to use with the credentials object requires the app to have the
     * GET_ACCOUNTS permission, which is requested here if it is not already
     * present. The AfterPermissionGranted annotation indicates that this
     * function will be rerun automatically whenever the GET_ACCOUNTS permission
     * is granted.
     */
    @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
    private fun chooseAccount(summary: String, des: String, startDate: String, endDate: String,
                              repeating: Boolean, reminder: Boolean, dateTime: Boolean) {
        if (EasyPermissions.hasPermissions(
                        context, Manifest.permission.GET_ACCOUNTS)) {
            val accountName = context.getPreferences(Context.MODE_PRIVATE)
                    .getString(PREF_ACCOUNT_NAME, null)
            if (accountName != null) {
                mCredential.selectedAccountName = accountName
                insertEvent(summary, des, startDate, endDate, repeating, reminder, dateTime)
            } else {
                // Start a dialog from which the user can choose an account
                lastAdded = arrayListOf(summary, des, startDate, endDate, repeating, reminder, dateTime)
                context.startActivityForResult(mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER)
                //insertEvent(summary, des, startDate, endDate, repeating, reminder, dateTime)
            }
        } else {
            // Request the GET_ACCOUNTS permission via a user dialog
            EasyPermissions.requestPermissions(
                    context,
                    "This app needs to access your Google account (via Contacts).",
                    REQUEST_PERMISSION_GET_ACCOUNTS,
                    Manifest.permission.GET_ACCOUNTS)
        }
    }

    /**
     * Respond to requests for permissions at runtime for API 23 and above.
     * @param requestCode The request code passed in
     * requestPermissions(android.app.Activity, String, int, String[])
     * @param permissions The requested permissions. Never null.
     * @param grantResults The grant results for the corresponding permissions
     * which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
     */
    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<String>,
                                            grantResults: IntArray) {
        EasyPermissions.onRequestPermissionsResult(
                requestCode, permissions, grantResults, this)
    }

    /**
     * Callback for when a permission is granted using the EasyPermissions
     * library.
     * @param requestCode The request code associated with the requested
     * permission
     * @param list The requested permission list. Never null.
     */
    override fun onPermissionsGranted(requestCode: Int, list: List<String>) {
        // Do nothing.
    }

    /**
     * Callback for when a permission is denied using the EasyPermissions
     * library.
     * @param requestCode The request code associated with the requested
     * permission
     * @param list The requested permission list. Never null.
     */
    override fun onPermissionsDenied(requestCode: Int, list: List<String>) {
        // Do nothing.
    }

    /**
     * Checks whether the device currently has a network connection.
     * @return true if the device has a network connection, false otherwise.
     */
    private fun isDeviceOnline(): Boolean {
        val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
        val networkInfo = connMgr!!.activeNetworkInfo
        return networkInfo != null && networkInfo.isConnected
    }

    /**
     * Check that Google Play services APK is installed and up to date.
     * @return true if Google Play Services is available and up to
     * date on this device; false otherwise.
     */
    private fun isGooglePlayServicesAvailable(): Boolean {
        val apiAvailability = GoogleApiAvailability.getInstance()
        val connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(context)
        return connectionStatusCode == ConnectionResult.SUCCESS
    }

    /**
     * Attempt to resolve a missing, out-of-date, invalid or disabled Google
     * Play Services installation via a user dialog, if possible.
     */
    private fun acquireGooglePlayServices() {
        val apiAvailability = GoogleApiAvailability.getInstance()
        val connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(context)
        if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
            showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode)
        }
    }


    /**
     * Display an error dialog showing that Google Play Services is missing
     * or out of date.
     * @param connectionStatusCode code describing the presence (or lack of)
     * Google Play Services on this device.
     */
    private fun showGooglePlayServicesAvailabilityErrorDialog(
            connectionStatusCode: Int) {
        val apiAvailability = GoogleApiAvailability.getInstance()
        val dialog = apiAvailability.getErrorDialog(
                context,
                connectionStatusCode,
                REQUEST_GOOGLE_PLAY_SERVICES)
        dialog.show()
    }
}

我在AndroidManifest.xml中设置的权限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />

我的MainActivity.kt中的onActivityResult函数:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (requestCode) {
            GoogleCalendar.REQUEST_GOOGLE_PLAY_SERVICES -> if (resultCode != Activity.RESULT_OK) {
                Toast.makeText(this, "This app requires Google Play Services. Please install " + "Google Play Services on your device and relaunch this app."
                , Toast.LENGTH_LONG).show()
            }
            de.schooldiary.flokol120.schooldiary.utils.GoogleCalendar.REQUEST_ACCOUNT_PICKER -> if (resultCode == Activity.RESULT_OK && data != null &&
                    data.extras != null) {
                val accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
                if (accountName != null) {
                    val settings = getPreferences(Context.MODE_PRIVATE)
                    val editor = settings.edit()
                    editor.putString(GoogleCalendar.PREF_ACCOUNT_NAME, accountName)
                    editor.apply()
                    gCalendar.mCredential.selectedAccountName = accountName
                }
                val list: ArrayList<Any> = GoogleCalendar.lastAdded
                if(list.isNotEmpty()){
                    gCalendar.insertEvent(list[0] as String, list[1] as String, list[2] as String,
                            list[3] as String, list[4] as Boolean, list[5] as Boolean, list[6] as Boolean)
                }
            }
            de.schooldiary.flokol120.schooldiary.utils.GoogleCalendar.REQUEST_AUTHORIZATION -> if (resultCode == Activity.RESULT_OK) {
                val list: ArrayList<Any> = GoogleCalendar.lastAdded
                if(list.isNotEmpty()){
                    gCalendar.insertEvent(list[0] as String, list[1] as String, list[2] as String,
                            list[3] as String, list[4] as Boolean, list[5] as Boolean, list[6] as Boolean)
                }
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

我在 build.gradle 中的实现:

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.0'
    implementation 'com.android.support:design:27.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:support-v4:27.1.0'
    implementation 'com.android.support:cardview-v7:27.1.0'
    implementation 'com.android.support:recyclerview-v7:27.1.0'
    implementation 'com.github.QuadFlask:colorpicker:0.0.13'
    implementation 'com.github.florent37:expansionpanel:1.0.6'
    implementation 'com.google.android.gms:play-services-auth:11.8.0'
    implementation 'pub.devrel:easypermissions:1.1.3'
    implementation('com.google.api-client:google-api-client-android:1.23.0') {
        exclude group: 'org.apache.httpcomponents'
    }
    implementation('com.google.apis:google-api-services-calendar:v3-rev292-1.23.0') {
        exclude group: 'org.apache.httpcomponents'
    }
    implementation 'com.google.firebase:firebase-ads:11.8.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

对于遇到此类问题的每个人: 启用新的 Google 签名功能时,您需要将 Google Play 管理中心的指纹添加到经过身份验证的指纹列表中。