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%d@example.com" 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 Fusce semper, sapien ut venenatis pellentesque, 163 lorem dui aliquam sapien, non pharetra diam neque id mi. 164 """ 165 } 166 } 167