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