title: Got Unknown URL content in Android
body: <p>I am new to Android. I wanted to practice my knowledge about Content Provide, but my app somehow crush and tell me there is an unknown URL content. I try to search for solution but still can't fix the problem...</p>
  
  <p>The logcat message:</p>
  
  <pre><code> 2020-05-25 20:10:24.547 15120-15120/com.example.punchinandout E/AndroidRuntime: FATAL EXCEPTION: main
          Process: com.example.punchinandout, PID: 15120
          java.lang.IllegalArgumentException: Unknown URL content://com.example.punchinandout.data/time
              at android.content.ContentResolver.insert(ContentResolver.java:1535)
              at com.example.punchinandout.MainActivity$1.onClick(MainActivity.java:65)
              at android.view.View.performClick(View.java:6256)
              at android.view.View$PerformClick.run(View.java:24701)
              at android.os.Handler.handleCallback(Handler.java:789)
              at android.os.Handler.dispatchMessage(Handler.java:98)
              at android.os.Looper.loop(Looper.java:164)
              at android.app.ActivityThread.main(ActivityThread.java:6541)
              at java.lang.reflect.Method.invoke(Native Method)
              at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
  </code></pre>
  
  <p>My provider in Mainifest:</p>
  
  <pre><code>&lt;provider
              android:authorities="com.example.punchinandout"
              android:name=".data.TimeProvider"
              android:exported="false"/&gt; 
  </code></pre>
  
  <p>My Content Provider class:</p>
  
  <pre><code>package com.example.punchinandout.data;
  
  import android.content.ContentProvider;
  import android.content.ContentUris;
  import android.content.ContentValues;
  import android.content.UriMatcher;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
  import android.net.Uri;
  import android.util.Log;
  
  import com.example.punchinandout.data.TimeContract.TimeEntry;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  
  public class TimeProvider extends ContentProvider {
      private static final String TAG = TimeProvider.class.toString();
  
      private static final int TIME = 100;
      private static final int TIME_ID = 101;
  
      private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  
      //Determine which content URI we have.
      static{
          sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME);
          sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME_ID);
      }
  
      private TimeHelper mDbHelper;
  
      @Override
      public boolean onCreate() {
          mDbHelper = new TimeHelper(getContext());
          return true;
      }
  
      @Nullable
      @Override
      public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
          SQLiteDatabase database = mDbHelper.getReadableDatabase();
          Cursor cursor;
          int match = sUriMatcher.match(uri);
  
          switch (match){
              case TIME:
                  cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                  break;
  
              case TIME_ID:
                  selection = TimeEntry._ID + "=?";
                  selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                  cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                  break;
  
              default:
                  throw new IllegalArgumentException("Cannot query with unknown URI: " + uri);
          }
          cursor.setNotificationUri(getContext().getContentResolver(), uri); //Set notification URI on cursor, so that the cursor will update when the URI is updated.
          return cursor;
      }
  
      @Nullable
      @Override
      public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
          int match = sUriMatcher.match(uri);
          switch (match){
              case TIME:
                  return insertTimeRecord(uri, values);
  
              default:
                  throw new IllegalArgumentException("Insertion is no support for " + uri);
          }
      }
  
      private Uri insertTimeRecord(Uri uri, ContentValues values){
          //Sanity check
          if (values.containsKey(TimeEntry.COLUMN_RECORD)){
              String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
              if (time_record == null){
                  throw new IllegalArgumentException("Time record cannot be empty.");
              }
          }
  
          //If there are no values to update, then don't try to update the database.
          if (values.size() == 0){
              return null;
          }
  
          SQLiteDatabase database = mDbHelper.getWritableDatabase();
          long newRowId = database.insert(TimeEntry.TABLE_NAME, null, values);
  
          //Check if the insertion is success.
          if (newRowId == -1){
              Log.e(TAG, "Insertion fail with " + uri);
              return null;
          }
  
          getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
          return ContentUris.withAppendedId(uri, newRowId);
      }
  
      @Override
      public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
          int match = sUriMatcher.match(uri);
          switch (match){
              case TIME:
                  return updateTimeRecord(uri, values, selection, selectionArgs);
  
              case TIME_ID:
                  selection = TimeEntry._ID + "=?";
                  selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                  return updateTimeRecord(uri, values, selection, selectionArgs);
  
              default:
                  throw new IllegalArgumentException("Update fail with " + uri);
          }
      }
  
      private int updateTimeRecord(Uri uri, ContentValues values, String selection, String[] selectionArgs){
          //Sanity check
          if (values.containsKey(TimeEntry.COLUMN_RECORD)){
              String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
              if (time_record == null){
                  return 0; //If the data is not inputted, update nothing in database.
              }
          }
  
          //If there are no values to update, then don't try to update the database.
          if (values.size() == 0){
              return 0;
          }
  
          SQLiteDatabase database = mDbHelper.getWritableDatabase();
          int rowUpdated = database.update(TimeEntry.TABLE_NAME, values, selection, selectionArgs);
  
          if (rowUpdated != 0){
              getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
          }
  
          return rowUpdated;
      }
  
      @Override
      public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
          int match = sUriMatcher.match(uri);
          switch (match){
              case TIME:
                  return deleteTimeRecord(uri, selection, selectionArgs);
  
              case TIME_ID:
                  selection = TimeEntry._ID + "=?";
                  selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                  return deleteTimeRecord(uri, selection, selectionArgs);
  
              default:
                  throw new IllegalArgumentException("Delete fail with " + uri);
          }
      }
  
      private int deleteTimeRecord(Uri uri, String selection, String[] selectionArgs){
          SQLiteDatabase database = mDbHelper.getWritableDatabase();
          int rowDelected = database.delete(TimeEntry.TABLE_NAME, selection, selectionArgs);
          if (rowDelected != 0){
              getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
          }
          return rowDelected;
      }
  
      @Nullable
      @Override
      public String getType(@NonNull Uri uri) {
          int match = sUriMatcher.match(uri);
          switch (match){
              case TIME:
                  return TimeEntry.CONTENT_LIST_TYPE;
  
              case TIME_ID:
                  return TimeEntry.CONTENT_ITEM_TYPE;
  
              default:
                  throw new IllegalArgumentException("Unknown URI " + uri + " with match " + match);
          }
      }
  }
  </code></pre>
  
  <p>My Contract class:</p>
  
  <pre><code>package com.example.punchinandout.data;
  
  import android.content.ContentResolver;
  import android.net.Uri;
  import android.provider.BaseColumns;
  
  public class TimeContract {
  
      private TimeContract(){};
  
      public static final String CONTENT_AUTHORITY = "com.example.punchinandout.data";
      public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
      public static final String CONTENT_PATH = "time";
  
      public static final class TimeEntry implements BaseColumns {
          public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
  
          //MIME type of all records.
          public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
  
          //MIME type of single record.
          public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
  
          public static final String TABLE_NAME = "time";
  
          public static final String _ID = BaseColumns._ID;
          public static final String COLUMN_RECORD = "record";
      }
  }
  </code></pre>
  
  <p>Can someone help me? :(</p>