1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pandora 18 19 import android.bluetooth.BluetoothManager 20 import android.content.ContentProviderOperation 21 import android.content.ContentValues 22 import android.content.Context 23 import android.provider.CallLog 24 import android.provider.CallLog.Calls.* 25 import android.provider.ContactsContract 26 import android.provider.ContactsContract.* 27 import android.provider.ContactsContract.CommonDataKinds.* 28 import java.io.Closeable 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.Dispatchers 31 import kotlinx.coroutines.cancel 32 import pandora.PBAPGrpc.PBAPImplBase 33 import pandora.PbapProto.* 34 35 @kotlinx.coroutines.ExperimentalCoroutinesApi 36 class Pbap(val context: Context) : PBAPImplBase(), Closeable { 37 private val TAG = "PandoraPbap" 38 39 private val scope: CoroutineScope 40 private val allowedDigits = ('0'..'9') 41 42 private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! 43 private val bluetoothAdapter = bluetoothManager.adapter 44 45 init { 46 // Init the CoroutineScope 47 scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 48 preparePBAPDatabase() 49 } 50 preparePBAPDatabasenull51 private fun preparePBAPDatabase() { 52 prepareContactList() 53 prepareCallLog() 54 } 55 prepareContactListnull56 private fun prepareContactList() { 57 var cursor = 58 context 59 .getContentResolver() 60 .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null) 61 62 if (cursor == null) return 63 64 if (cursor.getCount() >= CONTACT_LIST_SIZE) return // return if contacts are present 65 66 for (item in cursor.getCount() + 1..CONTACT_LIST_SIZE) { 67 addContact(item) 68 } 69 } 70 prepareCallLognull71 private fun prepareCallLog() { 72 // Delete existing call log 73 context.getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null) 74 75 addCallLogItem(MISSED_TYPE) 76 addCallLogItem(OUTGOING_TYPE) 77 } 78 addCallLogItemnull79 private fun addCallLogItem(callType: Int) { 80 var contentValues = 81 ContentValues().apply { 82 put(CallLog.Calls.NUMBER, generatePhoneNumber(PHONE_NUM_LENGTH)) 83 put(CallLog.Calls.DATE, System.currentTimeMillis()) 84 put(CallLog.Calls.DURATION, if (callType == MISSED_TYPE) 0 else 30) 85 put(CallLog.Calls.TYPE, callType) 86 put(CallLog.Calls.NEW, 1) 87 } 88 context.getContentResolver().insert(CallLog.Calls.CONTENT_URI, contentValues) 89 } 90 addContactnull91 private fun addContact(contactIndex: Int) { 92 val operations = arrayListOf<ContentProviderOperation>() 93 94 val displayName = String.format(DEFAULT_DISPLAY_NAME, contactIndex) 95 val phoneNumber = generatePhoneNumber(PHONE_NUM_LENGTH) 96 val emailID = String.format(DEFAULT_EMAIL_ID, contactIndex) 97 val note = String.format(DEFAULT_NOTE, contactIndex) 98 99 val rawContactInsertIndex = operations.size 100 operations.add( 101 ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 102 .withValue(RawContacts.ACCOUNT_TYPE, null) 103 .withValue(RawContacts.ACCOUNT_NAME, null) 104 .build() 105 ) 106 107 operations.add( 108 ContentProviderOperation.newInsert(Data.CONTENT_URI) 109 .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) 110 .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) 111 .withValue(StructuredName.DISPLAY_NAME, displayName) 112 .build() 113 ) 114 115 operations.add( 116 ContentProviderOperation.newInsert(Data.CONTENT_URI) 117 .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) 118 .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) 119 .withValue(Phone.NUMBER, phoneNumber) 120 .withValue(Phone.TYPE, Phone.TYPE_MOBILE) 121 .build() 122 ) 123 124 operations.add( 125 ContentProviderOperation.newInsert(Data.CONTENT_URI) 126 .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) 127 .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) 128 .withValue(Email.DATA, emailID) 129 .withValue(Email.TYPE, Email.TYPE_MOBILE) 130 .build() 131 ) 132 133 operations.add( 134 ContentProviderOperation.newInsert(Data.CONTENT_URI) 135 .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) 136 .withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) 137 .withValue(Note.NOTE, note) 138 .build() 139 ) 140 141 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations) 142 } 143 generatePhoneNumbernull144 private fun generatePhoneNumber(length: Int): String { 145 return buildString { repeat(length) { append(allowedDigits.random()) } } 146 } 147 closenull148 override fun close() { 149 // Deinit the CoroutineScope 150 scope.cancel() 151 } 152 153 companion object { 154 const val DEFAULT_DISPLAY_NAME = "Contact Name %d" 155 const val DEFAULT_EMAIL_ID = "user%[email protected]" 156 const val CONTACT_LIST_SIZE = 125 157 const val PHONE_NUM_LENGTH = 10 158 const val DEFAULT_NOTE = 159 """ 160 %d Lorem ipsum dolor sit amet, consectetur adipiscing elit. 161 Vivamus condimentum rhoncus est volutpat venenatis. 162 """ 163 } 164 } 165