/* * Copyright (C) 2022 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.pandora import android.bluetooth.BluetoothManager import android.content.ContentProviderOperation import android.content.ContentValues import android.content.Context import android.provider.CallLog import android.provider.CallLog.Calls.* import android.provider.ContactsContract import android.provider.ContactsContract.* import android.provider.ContactsContract.CommonDataKinds.* import java.io.Closeable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import pandora.PBAPGrpc.PBAPImplBase import pandora.PbapProto.* @kotlinx.coroutines.ExperimentalCoroutinesApi class Pbap(val context: Context) : PBAPImplBase(), Closeable { private val TAG = "PandoraPbap" private val scope: CoroutineScope private val allowedDigits = ('0'..'9') private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! private val bluetoothAdapter = bluetoothManager.adapter init { // Init the CoroutineScope scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) preparePBAPDatabase() } private fun preparePBAPDatabase() { prepareContactList() prepareCallLog() } private fun prepareContactList() { var cursor = context .getContentResolver() .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null) if (cursor == null) return if (cursor.getCount() >= CONTACT_LIST_SIZE) return // return if contacts are present for (item in cursor.getCount() + 1..CONTACT_LIST_SIZE) { addContact(item) } } private fun prepareCallLog() { // Delete existing call log context.getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null) addCallLogItem(MISSED_TYPE) addCallLogItem(OUTGOING_TYPE) } private fun addCallLogItem(callType: Int) { var contentValues = ContentValues().apply { put(CallLog.Calls.NUMBER, generatePhoneNumber(PHONE_NUM_LENGTH)) put(CallLog.Calls.DATE, System.currentTimeMillis()) put(CallLog.Calls.DURATION, if (callType == MISSED_TYPE) 0 else 30) put(CallLog.Calls.TYPE, callType) put(CallLog.Calls.NEW, 1) } context.getContentResolver().insert(CallLog.Calls.CONTENT_URI, contentValues) } private fun addContact(contactIndex: Int) { val operations = arrayListOf() val displayName = String.format(DEFAULT_DISPLAY_NAME, contactIndex) val phoneNumber = generatePhoneNumber(PHONE_NUM_LENGTH) val emailID = String.format(DEFAULT_EMAIL_ID, contactIndex) val note = String.format(DEFAULT_NOTE, contactIndex) val rawContactInsertIndex = operations.size operations.add( ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, null) .withValue(RawContacts.ACCOUNT_NAME, null) .build() ) operations.add( ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.DISPLAY_NAME, displayName) .build() ) operations.add( ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) .withValue(Phone.NUMBER, phoneNumber) .withValue(Phone.TYPE, Phone.TYPE_MOBILE) .build() ) operations.add( ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) .withValue(Email.DATA, emailID) .withValue(Email.TYPE, Email.TYPE_MOBILE) .build() ) operations.add( ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) .withValue(Note.NOTE, note) .build() ) context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations) } private fun generatePhoneNumber(length: Int): String { return buildString { repeat(length) { append(allowedDigits.random()) } } } override fun close() { // Deinit the CoroutineScope scope.cancel() } companion object { const val DEFAULT_DISPLAY_NAME = "Contact Name %d" const val DEFAULT_EMAIL_ID = "user%d@example.com" const val CONTACT_LIST_SIZE = 125 const val PHONE_NUM_LENGTH = 10 const val DEFAULT_NOTE = """ %d Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus condimentum rhoncus est volutpat venenatis. """ } }