Chapter 8 of ContentProvider

  1. Runtime permissions

    1. Android roughly divides common permissions into two categories: normal permissions and dangerous permissions

      • Ordinary permissions refer to those permissions that do not directly threaten users’ security and privacy, and the system automatically grants them to us, such as the permission to listen to startup broadcasts
      • Dangerous permissions are those that may affect user privacy or device security
    2. Dangerous permissions (11 groups of 30)

      Permissions group name Permission to name
      CALENDAR android.permission.READ_CALENDAR

      android.permission.WRITE_CALENDAR
      CALL_LOG android.permission.READ_CALL_LOG

      android.permission.WRITE_CALL_LOG
      CAMERA android.permission.CAMERA
      CONTACTS android.permission.READ_CONTACTS

      android.permission.WRITE_CONTACTS

      android.permission.GET_ACCOUNTS
      LOCATION android.permission.ACCESS_FINE_LOCATION

      android.permission.ACCESS_COARSE_LOCATION

      android.permission.ACCESS_BACKGROUND_LOCATION
      MICROPHONE android.permission.RECORD_AUDIO
      PHONE android.permission.READ_PHONE_STATE

      android.permission.READ_PHONE_NUMBERS

      android.permission.CALL_PHONE

      android.permission.ANSWER_PHONE_CALLS

      android.permission.ADD_VOICEMAIL

      android.permission.USE_SIP

      android.permission.ACCEPT_HANDOVER
      SENSORS android.permission.BODY_SENSORS
      ACTIVITY_RECOGNITION android.permission.ACTIVITY_RECOGNITION
      SMS android.permission.SEND_SMS

      android.permission.RECEIVE_SMS

      android.permission.READ_SMS

      android.permission.RECEIVE_MMS
      STORAGE android.permission.WRITE_EXTERNAL_STORAGE

      android.permission.READ_EXTERNAL_STORAGE

      android.permission.ACCESS_MEDIA_LOCATION
    3. Request permission at runtime

      1. Add permission declarations to XML files

        <uses-permission android:name="android.permission.CALL_PHONE" />
        Copy the code
      2. Check and obtain permissions

        if (ContextCompat.checkSelfPermission(
                this, Manifest.permission.CALL_PHONE ) ! = PackageManager.PERMISSION_GRANTED ) {// The second argument is an array of permissions. Multiple permissions can be requested
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)}Copy the code
      3. Request permission callback

        // The second argument is an array of permissions, and the third argument is an array of results of the corresponding permissions
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            when (requestCode) {
                1- > {if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        startCallIntent()
                    } else {
                        Toast.makeText(this."you've denied", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
        Copy the code
  2. ContentResolver

    1. The Uri is introduced

      Authority and PATH

      • Authority mainly distinguishes between different applications
      • Path distinguishes between different tables in the program
    2. Example Reading contacts

      contentResolver.query(
          Uri.withAppendedPath(
              ContactsContract.CommonDataKinds.Phone.CONTENT_URI, "1"),
              null.null.null.null)? .apply {while (moveToNext()) {
              contactList.add(
                  "${getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))} ${ getString( getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER ) ) }"
              )
          }
          adapter.notifyDataSetChanged()
          close()
      }
      Copy the code
  3. ContentProvider

    1. Uri

      1. The end of path indicates that all data in the table is expected to be accessed, and the end of ID indicates that data in the table with corresponding IDS are expected to be accessed.

        The content: / / com. Youngly. Firstlineofcode/table all the data in the table

        The content: / / com. Youngly. Firstlineofcode/table/id of 1 data in the table 1

      2. We can use wildcards to match both formats of content URIs as follows

        * matches any character of any length

        # matches a number of any length

    2. GetType () method

      Get the MIME type of the Uri object. The MIME string corresponding to a content URI consists of three parts, as follows

      • It must start with VND
      • If the content ends in a path, it is followed by Android.cursor.dir /; If the content URI ends in id, it is followed by Android.cursor.item /
      • Finally connect to VND..

      The content: / / com. Youngly. Firstlineofcode/table MIME

      vnd.android.cursor.dir/vnd.com.youngly.firstlineofcode.table

      The content: / / com. Youngly. Firstlineofcode/table / 1 MIME

      vnd.android.cursor.item/vnd.com.youngly.firstlineofcode.table

      The MIME type is primarily the data field of the Activity’s intent-filter

      MIME types are used in an Activity to specify the type of file that the current Activity supports opening

      A MIME type is a string separated by a “/”. The part before the “/” is what the system recognizes. It is the data type of a variable that we define. As for the “/” after the part is our own arbitrary definition of “variable name”.

      Override getType() to do this:

      <activity android:name=".chapter8.ContentResolverActivity" >
          <intent-filter>
              <action android:name="com.youngly.providertypetest"/>
              <category android:name="android.intent.category.DEFAULT"/>// mimeType in the activity's data is getType<data android:mimeType="vnd.android.cursor.item/vnd.com.youngly.firstlineofcode.book"/>
          </intent-filter>
      </activity>
      Copy the code
      // You can jump to mimeType
      val intent = Intent(packageName)
      intent.action = "com.youngly.providertypetest"
      // Use getType to match the mimeType in data
      intent.data = Uri.parse("content://com.youngly.firstlineofcode/book/1")
      startActivity(intent)
      Copy the code

      When is getType used?

      1. The activity is implicitly called to pass in data. And the data is the URI parameter of a ContentProvider
      2. To prevent the activity from being unable to process data, the activity needs to set MIME to perform data validation
      3. In order for AcVITY to validate the custom ContentProvider, we need to implement the getType of ContentProvider
      package com.youngly.firstlineofcode.chapter8
      
      import android.content.ContentProvider
      import android.content.ContentValues
      import android.content.UriMatcher
      import android.database.Cursor
      import android.net.Uri
      import com.youngly.firstlineofcode.chapter7.database.MyDatabaseHelper
      
      class BookStoreContentProvider : ContentProvider() {
      
          private val bookDir = 0
          private val bookItem = 1
          private val categoryDir = 2
          private val categoryItem = 3
          private val authority = "com.youngly.firstlineofcode"
          private lateinit var dbHelper: MyDatabaseHelper
      
          private val uriMatcher by lazy {
              val matcher = UriMatcher(UriMatcher.NO_MATCH)
              matcher.addURI(authority, "book", bookDir)
              // Wildcard, any line of data
              matcher.addURI(authority, "book/#", bookItem)
              matcher.addURI(authority, "category", categoryDir)
              matcher.addURI(authority, "category/#", categoryItem)
              matcher
          }
      
          override fun onCreate(a)= context? .let {// Initialize the database and upgrade it
              dbHelper = MyDatabaseHelper(it, "BookStore.db".2)
              true
          } ?: false
      
          override fun query(
              uri: Uri, projection: Array<String>? , selection:String? , selectionArgs:Array<String>? , sortOrder:String?).: Cursor? =
              dbHelper.let {
                  val db = it.readableDatabase
                  var cursor = when (uriMatcher.match(uri)) {
                      bookDir -> db.query(
                          "Book", projection, selection, selectionArgs, null.null, sortOrder
                      )
                      bookItem -> {
                          val bookId = uri.pathSegments[1]
                          db.query("Book", projection, "id = ?", arrayOf(bookId), null.null, sortOrder)
                      }
                      categoryDir -> db.query(
                          "Category", projection, selection, selectionArgs, null.null, sortOrder
                      )
                      categoryItem -> {
                          val categoryId = uri.pathSegments[1]
                          db.query(
                              "Category",
                              projection,
                              "id = ?",
                              arrayOf(categoryId),
                              null.null,
                              sortOrder
                          )
                      }
                      else -> null
                  }
                  cursor
              }
      
          override fun insert(uri: Uri, values: ContentValues?). = dbHelper.let {
              val db = it.writableDatabase
              val uriReturn = when (uriMatcher.match(uri)) {
                  bookDir, bookItem -> {
                      val insertId = db.insert("Book".null, values)
                      Uri.parse("content://$authority/book/$insertId")
                  }
                  categoryDir, categoryItem -> {
                      val insertId = db.insert("Category".null, values)
                      Uri.parse("content://$authority/category/$insertId")}else -> null
              }
              uriReturn
          }
      
          override fun update(
              uri: Uri, values: ContentValues? , selection:String? , selectionArgs:Array<String>? = dbHelper.let {
              val db = it.writableDatabase
              val updateId = when (uriMatcher.match(uri)) {
                  bookDir -> db.update("Book", values, selection, selectionArgs)
                  bookItem -> {
                      val bookId = uri.pathSegments[1]
                      db.update("Book", values, "id = ?", arrayOf(bookId))
                  }
                  categoryDir -> db.update("Category", values, selection, selectionArgs)
                  categoryItem -> {
                      val categoryId = uri.pathSegments[1]
                      db.update("Category", values, "id = ?", arrayOf(categoryId))
                  }
                  else -> 0
              }
              updateId
          }
      
          override fun delete(uri: Uri, selection: String? , selectionArgs:Array<String>? =
              dbHelper.let {
                  val db = it.writableDatabase
                  val deleteId = when (uriMatcher.match(uri)) {
                      bookDir -> db.delete("Book", selection, selectionArgs)
                      bookItem -> {
                          val bookId = uri.pathSegments[1]
                          db.delete("Book"."id = ?", arrayOf(bookId))
                      }
                      categoryDir -> db.delete("Category", selection, selectionArgs)
                      categoryItem -> {
                          val categoryId = uri.pathSegments[1]
                          db.delete("Category"."id = ?", arrayOf(categoryId))
                      }
                      else -> 0
                  }
                  deleteId
              }
      
          override fun getType(uri: Uri): String? = when (uriMatcher.match(uri)) {
              bookDir -> "vnd.android.cursor.dir/vnd.$authority.book"
              bookItem -> "vnd.android.cursor.item/vnd.$authority.book"
              categoryDir -> "vnd.android.cursor.dir/vnd.$authority.category"
              categoryItem -> "vnd.android.cursor.item/vnd.$authority.category"
              else -> null}}Copy the code
  4. Kotlin classroom

    1. The generic

      1. A generic class

        class MyClass<T> {
            fun method(params: T): T {
                return params
            }
        }
        Copy the code

        call

        val myClass = MyClass<Int> ()val method = myClass.method(2)
        Copy the code
      2. Generic method

        class MyClass {
            fun <T> method(params: T): T {
                return params
            }
        }
        Copy the code

        call

        val myClass = MyClass()
        val method = myClass.method<Int> (2)
        Copy the code
      3. Type restrictions

        The upper bound of the generic type is set to type Number

        fun <T : Number> method(params: T): T {
            return params
        }
        Copy the code
    2. Class delegates and delegate properties

      1. Commissioned by class

        Delegate is a design pattern. The basic idea is that an operating object does not handle a certain piece of logic itself, but delegates work to another auxiliary object.

        class MySet<T>(val helper: HashSet<T>) : Set<T> {
            override val size: Int
                get() = helper.size
        
            override fun contains(element: T) = helper.contains(element)
        
            override fun containsAll(elements: Collection<T>) = helper.containsAll(elements)
        
            override fun isEmpty(a) = helper.isEmpty()
        
            override fun iterator(a) = helper.iterator()
        }
        Copy the code

        A helper is an auxiliary object. This is essentially a delegation model.

        The drawback of this writing method is that there are too many methods to be realized, and it is tedious to write.

        class MySet<T>(val helper: HashSet<T>) : Set<T> by helper {
            
        }
        Copy the code

        In Kotlin, the delegate keyword is BY, and we simply use the by keyword at the end of the interface declaration, followed by the delegate helper object, to avoid a lot of template code that we wrote earlier.

        If we need to reimplement a method, we can simply rewrite that method separately.

        class MySet<T>(val helper: HashSet<T>) : Set<T> by helper {
        
            override fun isEmpty(a): Boolean {
                TODO("Not yet implemented")}}Copy the code
      2. Attribute to entrust

        The core of attribute delegation is to delegate the concrete implementation of an attribute to another class

        The Delegate class defines:

        class Delegate {
            var propValue: Any? = null
        
          	// The first parameter indicates the class in which the Delegate function is used. The second parameter is an attribute manipulation class in Kotlin, which allows the user to obtain the values associated with various attributes. >
            operator fun getValue(thisRef: Any? , property:KProperty< * >): Any? {
                return propValue
            }
        		// The last argument represents the value to be assigned to the delegate property
            operator fun setValue(thisRef: Any? , property:KProperty<*>, value: Any?). {
                propValue = value
            }
        }
        Copy the code

        Implement your own lazy

        Let’s define a KT file

        class Lazy<T>(val block: () -> T) {
            var value: Any? = null
            operator fun getValue(any: Any? , prop:KProperty< * >): T {
                if (value == null) {
                    value = block()
                }
                return value as T
            }
        }
        Copy the code

        Since lazy loading techniques do not assign values to attributes, there is no need to provide a setValue() method

        fun <T> later(block: () -> T) = Lazy(block)
        Copy the code