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