java .io.IOException: невозможно создать каталог: / storage / emulated / 0 / tokens - PullRequest
0 голосов
/ 14 февраля 2020

Я пытаюсь подключить свое приложение Android к Google Calendars. Я следовал учебному пособию quick start , попросил у пользователя разрешения на запись во внешнее хранилище, но я не могу запустить код, не выдав исключение java .io.IOException.

Мой код выглядит следующим образом:

GoogleCalendarModule.kt

class GoogleCalendarModule {
    var APPLICATION_NAME: String = "Conglobo"
    var JSON_FACTORY: JacksonFactory = JacksonFactory.getDefaultInstance()
    var TOKENS_DIRECTORY_PATH: String = "./tokens"

    var SCOPES: List<String> = Collections.singletonList(CalendarScopes.CALENDAR_READONLY)
    private var CREDENTIALS_FILE_PATH: String = "/credentials.json"

    fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): com.google.api.client.auth.oauth2.Credential? {

        val inputStream: InputStream = this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
            ?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")

        val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))

        val tokenFolder = File(getExternalStorageDirectory(), File.separator.toString() + TOKENS_DIRECTORY_PATH)
        if (!tokenFolder.exists()) {
            tokenFolder.mkdirs()
        }

        val flow: GoogleAuthorizationCodeFlow = GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
            .setDataStoreFactory(FileDataStoreFactory(tokenFolder))
            .setAccessType("offline")
            .build();

        val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()

        return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
    }

    fun doSomething() {
        val HTTP_TRANSPORT = NetHttpTransport()
        val service: Calendar = Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
            .setApplicationName(APPLICATION_NAME)
            .build()

        val now = DateTime(System.currentTimeMillis())
        val events: Events = service.events().list("primary")
            .setMaxResults(10)
            .setTimeMin(now)
            .setOrderBy("startTime")
            .setSingleEvents(true)
            .execute()

        val items: List<Event> = events.items
        if (items.isEmpty()) {
            println("No events")
        } else {
            println("Upcoming events")
            for(event: Event in items) {
                var start: DateTime = event.start.dateTime
                if(start == null) {
                    start = event.start.date
                }

                print(event.summary)
            }
        }
    }
}

Затем я проверяю текущие разрешения в MainActivity

MainActivity.kt

class MainActivity: AppCompatActivity(), ITeamFragmentDelegate {

    @Inject
    lateinit var teamInfoModule: TeamInfoModule;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_GRANTED) {
                print("Permission is granted");
            } else {

                print("Permission is revoked");
                ActivityCompat.requestPermissions(this, Array<String>(1) { Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            }
        }


        GoogleCalendarModule().doSomething()

        setContentView(R.layout.activity_main)
        DaggerServiceModuleComponent.create().inject(this)

        val teamArrayList: ArrayList<Team> = this.teamInfoModule.getAllTeamData()

        for(team: Team in teamArrayList) {
            val bundle = Bundle()
            val teamFragment = TeamFragment()

            bundle.putParcelable("teamData", team)
            teamFragment.arguments = bundle

            supportFragmentManager.beginTransaction()
                .add(R.id.root_container, teamFragment)
                .commitAllowingStateLoss()
        }
    }

    override fun onTeamClicked(fragment: TeamFragment, team: Team) {
        val intent = Intent(this, ViewTeamBacklogActivity::class.java)
        intent.putExtra("teamId", team.id)
        startActivity(intent)
    }
}

И у меня даже есть разрешения, объявленные в AndroidManifest. xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bluelightlite">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ViewTeamBacklogActivity">

        </activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Я искал вокруг SO, и это, кажется, общая проблема, но ни одно из решений, которые я ' мы нашли работали. Может ли кто-нибудь помочь, пожалуйста?

Ответы [ 2 ]

0 голосов
/ 20 февраля 2020

Нашел проблему.

Начиная с Android SDK 23 и далее (я полагаю). Рефакторинг GoogleCalendarModule.kt к этому:

package com.example.bluelightlite.modules

import android.content.Context
import com.example.bluelightlite.constants.APPLICATION_NAME
import com.example.bluelightlite.constants.JSON_FACTORY
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.DateTime
import com.google.api.services.calendar.Calendar
import com.google.api.services.calendar.model.Event
import com.google.api.services.calendar.model.Events

class GoogleCalendarsServiceModule constructor(context: Context) {

    private val googleCredentialsUtilityModule = GoogleCredentialsUtilityModule(context)

    fun getEvents(): List<Event> {

        val httpTransport = NetHttpTransport()
        val service: Calendar = Calendar.Builder(httpTransport, JSON_FACTORY, this.googleCredentialsUtilityModule.getCredentials(httpTransport))
            .setApplicationName(APPLICATION_NAME)
            .build()

        val now = DateTime(System.currentTimeMillis())
        val events: Events = service.events().list("primary")
            .setMaxResults(10)
            .setTimeMin(now)
            .setOrderBy("startTime")
            .setSingleEvents(true)
            .execute()

        return events.items;
    }
}

И создание файла GoogleCredentialsUtilityModule.kt, который выглядит следующим образом:

package com.example.bluelightlite.modules

import android.content.Context
import com.example.bluelightlite.constants.CREDENTIALS_FILE_PATH
import com.example.bluelightlite.constants.JSON_FACTORY
import com.example.bluelightlite.constants.SCOPES
import com.example.bluelightlite.constants.TOKENS_DIRECTORY_PATH
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.store.FileDataStoreFactory
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.InputStreamReader

class GoogleCredentialsUtilityModule constructor(private val context: Context) {

    /**
     * Creates an authorized Credential object.
     * @param HTTP_TRANSPORT The network HTTP Transport.
     * @return An authorized Credential object.
     * @throws java.io.IOException If the credentials.json file cannot be found.
     */
    fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): Credential {

        val inputStream: InputStream = getCredentialsAsInputStream()
        val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))

        createTokenFolderIfMissing()

        val authorisationFlow: GoogleAuthorizationCodeFlow = getAuthorisationFlow(HTTP_TRANSPORT, clientSecrets)
        val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()

        return AuthorizationCodeInstalledApp(authorisationFlow, receiver).authorize("freeman.marc880@gmail.com")
    }

    private fun getCredentialsAsInputStream(): InputStream {
        return this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
            ?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
    }

    private fun createTokenFolderIfMissing() {
        val tokenFolder = getTokenFolder()
        if (!tokenFolder.exists()) {
            tokenFolder.mkdir()
        }
    }

    private fun getTokenFolder(): File {
        return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
    }

    private fun getAuthorisationFlow(HTTP_TRANSPORT: NetHttpTransport, clientSecrets: GoogleClientSecrets): GoogleAuthorizationCodeFlow {
        return GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
            .setDataStoreFactory(FileDataStoreFactory(getTokenFolder()))
            .setAccessType("offline")
            .build()
    }
}

, заставляет его работать.

Я передал контекст в модуль учетных данных Google и попросил его извлечь каталог внешнего хранилища из текущего контекста в этой строке кода:

  private fun getTokenFolder(): File {
        return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
    }
0 голосов
/ 14 февраля 2020

Не знаю, полезно ли это, но вы можете попробовать установить свой targetSdkVersion Менее 23?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...