/* * Copyright (C) 2025 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.privatespace.filetransfer import android.app.Service import android.content.Intent import android.net.Uri import android.os.Environment import android.os.IBinder import android.util.Log import java.util.ArrayList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch /** * An object responsible for starting a foreground service to copy/move the user selected files into * private space. */ class FileTransferService( private val serviceScope: CoroutineScope = CoroutineScope(Dispatchers.IO) ) : Service() { companion object { /** * A boolean extra noting whether this transfer is a copy(true) or a move(false) operation. */ const val KEEP_ORIGINAL_EXTRA: String = "keep_original_extra" /** An array extra to get the uris of the file to transfer. */ const val SOURCE_URIS_EXTRA: String = "source_uris_extra" /** * Target relative path where the given file(s) should be saved. This path should be inline * with guidelines for {@link MediaStore.MediaColumns.RELATIVE_PATH} */ const val DESTINATION_PATH_EXTRA: String = "destination_path" private const val TAG: String = "FileTransferService" } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { val uris: ArrayList? = intent.getParcelableArrayListExtra(SOURCE_URIS_EXTRA, Uri::class.java) val keepOriginal = intent.getBooleanExtra(KEEP_ORIGINAL_EXTRA, true) val destinationPath = intent.getStringExtra(DESTINATION_PATH_EXTRA) ?: Environment.DIRECTORY_DOWNLOADS uris?.let { val numberOfFiles = it.size if (numberOfFiles == 0) { Log.e(TAG, "No files to transfer") return@let } val notificationsHelper = NotificationsHelper(applicationContext) notificationsHelper.createNotificationChannel() val fileTransferImpl = FileTransferManagerImpl(notificationsHelper, applicationContext) serviceScope.launch { startForeground( NotificationsHelper.NOTIFICATION_ID, notificationsHelper.buildProgressNotification(0, numberOfFiles, keepOriginal), ) fileTransferImpl.transferFiles(it, keepOriginal, destinationPath) stopForeground(STOP_FOREGROUND_DETACH) stopSelf() } } ?: // TODO(b/401000421): Notify the user that we are unable to read the files Log.e(TAG, "Unable to parse uris from the intent: $intent") return START_NOT_STICKY } override fun onBind(intent: Intent?): IBinder? { return null } }