From 638e1a4c94bbf59e2f693aac2ef7fc5705573c49 Mon Sep 17 00:00:00 2001 From: Aleksey Bogomolov Date: Fri, 23 Jun 2017 10:55:55 +0300 Subject: [PATCH] kotlin sample module --- sample-kotlin/.gitignore | 1 + sample-kotlin/build.gradle | 36 ++++ sample-kotlin/src/main/AndroidManifest.xml | 25 +++ .../dispatcher/samplekotlin/MainActivity.kt | 102 ++++++++++ .../samplekotlin/camera/CameraPreview.java | 150 ++++++++++++++ .../camera/CameraPreviewFragment.java | 135 +++++++++++++ .../contacts/ContactsFragment.java | 188 ++++++++++++++++++ .../src/main/res/layout/activity_main.xml | 30 +++ .../src/main/res/layout/fragment_camera.xml | 17 ++ .../src/main/res/layout/fragment_contacts.xml | 39 ++++ .../src/main/res/layout/fragment_main.xml | 32 +++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../src/main/res/values-w820dp/dimens.xml | 6 + sample-kotlin/src/main/res/values/dimens.xml | 6 + sample-kotlin/src/main/res/values/strings.xml | 20 ++ .../src/main/res/values/template-styles.xml | 3 + settings.gradle | 2 +- 20 files changed, 791 insertions(+), 1 deletion(-) create mode 100644 sample-kotlin/.gitignore create mode 100644 sample-kotlin/build.gradle create mode 100644 sample-kotlin/src/main/AndroidManifest.xml create mode 100644 sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/MainActivity.kt create mode 100644 sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreview.java create mode 100644 sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreviewFragment.java create mode 100644 sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/contacts/ContactsFragment.java create mode 100644 sample-kotlin/src/main/res/layout/activity_main.xml create mode 100644 sample-kotlin/src/main/res/layout/fragment_camera.xml create mode 100644 sample-kotlin/src/main/res/layout/fragment_contacts.xml create mode 100644 sample-kotlin/src/main/res/layout/fragment_main.xml create mode 100644 sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample-kotlin/src/main/res/values-w820dp/dimens.xml create mode 100644 sample-kotlin/src/main/res/values/dimens.xml create mode 100644 sample-kotlin/src/main/res/values/strings.xml create mode 100644 sample-kotlin/src/main/res/values/template-styles.xml diff --git a/sample-kotlin/.gitignore b/sample-kotlin/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/sample-kotlin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample-kotlin/build.gradle b/sample-kotlin/build.gradle new file mode 100644 index 00000000..c1ba12ee --- /dev/null +++ b/sample-kotlin/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion COMPILE_SDK_VERSION + buildToolsVersion BUILD_TOOLS_VERSION + defaultConfig { + applicationId "permissions.dispatcher.samplekotlin" + targetSdkVersion TARGET_SDK_VERSION + minSdkVersion MIN_SDK_VERSION + versionCode 1 + versionName "1.0" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + lintOptions { + abortOnError false + } +} + +kapt { + generateStubs = true +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION" + compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" + compile (project(':library')) { + exclude module: "support-v13" + } + compile project(':processor') + + kapt project(':processor') +} diff --git a/sample-kotlin/src/main/AndroidManifest.xml b/sample-kotlin/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a6f218c6 --- /dev/null +++ b/sample-kotlin/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/MainActivity.kt b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/MainActivity.kt new file mode 100644 index 00000000..400bce77 --- /dev/null +++ b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/MainActivity.kt @@ -0,0 +1,102 @@ +package permissions.dispatcher.samplekotlin + +import android.Manifest +import android.os.Bundle +import android.support.annotation.StringRes +import android.support.v7.app.AlertDialog +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.Toast +import permissions.dispatcher.* +import permissions.dispatcher.samplekotlin.camera.CameraPreviewFragment +import permissions.dispatcher.samplekotlin.contacts.ContactsFragment + +@RuntimePermissions +class MainActivity : AppCompatActivity(), View.OnClickListener { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + findViewById(R.id.button_camera).setOnClickListener(this) + findViewById(R.id.button_contacts).setOnClickListener(this) + } + + override fun onClick(v: View) { + when (v.id) { + R.id.button_camera -> { + // NOTE: delegate the permission handling to generated method + MainActivityPermissionsDispatcher.showCameraWithCheck(this) + } + + R.id.button_contacts -> { + // NOTE: delegate the permission handling to generated method + MainActivityPermissionsDispatcher.showContactsWithCheck(this) + } + + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + // NOTE: delegate the permission handling to generated method + MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults) + } + + @NeedsPermission(Manifest.permission.CAMERA) + fun showCamera() { + // NOTE: Perform action that requires the permission. If this is run by PermissionsDispatcher, the permission will have been granted + supportFragmentManager.beginTransaction() + .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance()) + .addToBackStack("camera") + .commitAllowingStateLoss() + } + + @NeedsPermission(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) + fun showContacts() { + // NOTE: Perform action that requires the permission. + // If this is run by PermissionsDispatcher, the permission will have been granted + supportFragmentManager.beginTransaction() + .replace(R.id.sample_content_fragment, ContactsFragment.newInstance()) + .addToBackStack("contacts") + .commitAllowingStateLoss() + } + + @OnShowRationale(Manifest.permission.CAMERA) + fun showRationaleForCamera(request: PermissionRequest) { + // NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog. + // Call proceed() or cancel() on the provided PermissionRequest to continue or abort + showRationaleDialog(R.string.permission_camera_rationale, request) + } + + @OnShowRationale(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) + fun showRationaleForContact(request: PermissionRequest) { + // NOTE: Show a rationale to explain why the permission is needed, e.g. with a dialog. + // Call proceed() or cancel() on the provided PermissionRequest to continue or abort + showRationaleDialog(R.string.permission_contacts_rationale, request) + } + + @OnPermissionDenied(Manifest.permission.CAMERA) + fun onCameraDenied() { + // NOTE: Deal with a denied permission, e.g. by showing specific UI + // or disabling certain functionality + Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show() + } + + @OnNeverAskAgain(Manifest.permission.CAMERA) + fun onCameraNeverAskAgain() { + Toast.makeText(this, R.string.permission_camera_never_askagain, Toast.LENGTH_SHORT).show() + } + + fun onBackClick(view: View) { + supportFragmentManager.popBackStack() + } + + private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) { + AlertDialog.Builder(this) + .setPositiveButton(R.string.button_allow) { dialog, which -> request.proceed() } + .setNegativeButton(R.string.button_deny) { dialog, which -> request.cancel() } + .setCancelable(false) + .setMessage(messageResId) + .show() + } +} diff --git a/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreview.java b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreview.java new file mode 100644 index 00000000..48f2947e --- /dev/null +++ b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreview.java @@ -0,0 +1,150 @@ +/* +* Copyright 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package permissions.dispatcher.samplekotlin.camera; + +import android.content.Context; +import android.hardware.Camera; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import java.io.IOException; + +/** + * Camera preview that displays a {@link Camera}. + * + * Handles basic lifecycle methods to display and stop the preview. + *

+ * Implementation is based directly on the documentation at + * http://developer.android.com/guide/topics/media/camera.html + */ +public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { + + private static final String TAG = "CameraPreview"; + private SurfaceHolder mHolder; + private Camera mCamera; + private Camera.CameraInfo mCameraInfo; + private int mDisplayOrientation; + + public CameraPreview(Context context, Camera camera, Camera.CameraInfo cameraInfo, + int displayOrientation) { + super(context); + + // Do not initialise if no camera has been set + if (camera == null || cameraInfo == null) { + return; + } + setCamera(camera, cameraInfo, displayOrientation); + + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed. + mHolder = getHolder(); + mHolder.addCallback(this); + } + + public void surfaceCreated(SurfaceHolder holder) { + // The Surface has been created, now tell the camera where to draw the preview. + try { + mCamera.setPreviewDisplay(holder); + mCamera.startPreview(); + Log.d(TAG, "Camera preview started."); + } catch (IOException e) { + Log.d(TAG, "Error setting camera preview: " + e.getMessage()); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + // empty. Take care of releasing the Camera preview in your activity. + } + + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + // If your preview can change or rotate, take care of those events here. + // Make sure to stop the preview before resizing or reformatting it. + + if (mHolder.getSurface() == null) { + // preview surface does not exist + Log.d(TAG, "Preview surface does not exist"); + return; + } + + // stop preview before making changes + try { + mCamera.stopPreview(); + Log.d(TAG, "Preview stopped."); + } catch (Exception e) { + // ignore: tried to stop a non-existent preview + Log.d(TAG, "Error starting camera preview: " + e.getMessage()); + } + + int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation); + mCamera.setDisplayOrientation(orientation); + + try { + mCamera.setPreviewDisplay(mHolder); + mCamera.startPreview(); + Log.d(TAG, "Camera preview started."); + } catch (Exception e) { + Log.d(TAG, "Error starting camera preview: " + e.getMessage()); + } + } + + /** + * Calculate the correct orientation for a {@link Camera} preview that is displayed on screen. + * + * Implementation is based on the sample code provided in + * {@link Camera#setDisplayOrientation(int)}. + */ + public static int calculatePreviewOrientation(Camera.CameraInfo info, int rotation) { + int degrees = 0; + + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + } + + int result; + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { // back-facing + result = (info.orientation - degrees + 360) % 360; + } + + return result; + } + + public void setCamera(Camera camera, Camera.CameraInfo cameraInfo, int displayOrientation) { + // Do not reinitialise if no camera has been set + if (camera == null || cameraInfo == null) { + return; + } + mCamera = camera; + mCameraInfo = cameraInfo; + mDisplayOrientation = displayOrientation; + } +} diff --git a/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreviewFragment.java b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreviewFragment.java new file mode 100644 index 00000000..28e73ba7 --- /dev/null +++ b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/camera/CameraPreviewFragment.java @@ -0,0 +1,135 @@ +/* +* Copyright 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package permissions.dispatcher.samplekotlin.camera; + +import android.hardware.Camera; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import permissions.dispatcher.samplekotlin.R; + + +/** + * Displays a {@link CameraPreview} of the first {@link Camera}. + * An error message is displayed if the Camera is not available. + *

+ * This Fragment is only used to illustrate that access to the Camera API has been granted (or + * denied) as part of the runtime permissions model. It is not relevant for the use of the + * permissions API. + *

+ * Implementation is based directly on the documentation at + * http://developer.android.com/guide/topics/media/camera.html + */ +public class CameraPreviewFragment extends Fragment { + + private static final String TAG = "CameraPreview"; + + /** + * Id of the camera to access. 0 is the first camera. + */ + private static final int CAMERA_ID = 0; + + private CameraPreview mPreview; + private Camera mCamera; + + public static CameraPreviewFragment newInstance() { + return new CameraPreviewFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_camera, null); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initCamera(); + } + + private void initCamera() { + mCamera = getCameraInstance(CAMERA_ID); + Camera.CameraInfo cameraInfo = null; + + if (mCamera != null) { + // Get camera info only if the camera is available + cameraInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(CAMERA_ID, cameraInfo); + } + + // Get the rotation of the screen to adjust the preview image accordingly. + final int displayRotation = getActivity().getWindowManager().getDefaultDisplay() + .getRotation(); + + if (getView() == null) { + return; + } + + FrameLayout preview = (FrameLayout) getView().findViewById(R.id.camera_preview); + preview.removeAllViews(); + + if (mPreview == null) { + // Create the Preview view and set it as the content of this Activity. + mPreview = new CameraPreview(getActivity(), mCamera, cameraInfo, displayRotation); + } else { + mPreview.setCamera(mCamera, cameraInfo, displayRotation); + } + + preview.addView(mPreview); + } + + @Override + public void onResume() { + super.onResume(); + if (mCamera == null) { + initCamera(); + } + } + + @Override + public void onPause() { + super.onPause(); + // Stop camera access + releaseCamera(); + } + + /** A safe way to get an instance of the Camera object. */ + public static Camera getCameraInstance(int cameraId) { + Camera c = null; + try { + c = Camera.open(cameraId); // attempt to get a Camera instance + } catch (Exception e) { + // Camera is not available (in use or does not exist) + Log.d(TAG, "Camera " + cameraId + " is not available: " + e.getMessage()); + } + return c; // returns null if camera is unavailable + } + + private void releaseCamera() { + if (mCamera != null) { + mCamera.release(); // release the camera for other applications + mCamera = null; + } + } +} diff --git a/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/contacts/ContactsFragment.java b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/contacts/ContactsFragment.java new file mode 100644 index 00000000..f0dbedf8 --- /dev/null +++ b/sample-kotlin/src/main/java/permissions/dispatcher/samplekotlin/contacts/ContactsFragment.java @@ -0,0 +1,188 @@ +/* +* Copyright 2015 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package permissions.dispatcher.samplekotlin.contacts; + +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import permissions.dispatcher.samplekotlin.R; + +import java.util.ArrayList; + +/** + * Displays the first contact stored on the device and contains an option to add a dummy contact. + *

+ * This Fragment is only used to illustrate that access to the Contacts ContentProvider API has + * been granted (or denied) as part of the runtime permissions model. It is not relevant for the + * use + * of the permissions API. + *

+ * This fragments demonstrates a basic use case for accessing the Contacts Provider. The + * implementation is based on the training guide available here: + * https://developer.android.com/training/contacts-provider/retrieve-names.html + */ +public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks { + + private static final String TAG = "Contacts"; + private TextView mMessageText = null; + + private static String DUMMY_CONTACT_NAME = "__DUMMY CONTACT from runtime permissions sample"; + + /** + * Projection for the content provider query includes the id and primary name of a contact. + */ + private static final String[] PROJECTION = {ContactsContract.Contacts._ID, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}; + /** + * Sort order for the query. Sorted by primary name in ascending order. + */ + private static final String ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"; + + + /** + * Creates a new instance of a ContactsFragment. + */ + public static ContactsFragment newInstance() { + return new ContactsFragment(); + } + + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_contacts, container, false); + + mMessageText = (TextView) rootView.findViewById(R.id.contact_message); + + // Register a listener to add a dummy contact when a button is clicked. + Button button = (Button) rootView.findViewById(R.id.contact_add); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + insertDummyContact(); + } + }); + + // Register a listener to display the first contact when a button is clicked. + button = (Button) rootView.findViewById(R.id.contact_load); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + loadContact(); + } + }); + return rootView; + } + + /** + * Restart the Loader to query the Contacts content provider to display the first contact. + */ + private void loadContact() { + getLoaderManager().restartLoader(0, null, this); + } + + /** + * Initialises a new {@link CursorLoader} that queries the {@link ContactsContract}. + */ + @Override + public Loader onCreateLoader(int i, Bundle bundle) { + return new CursorLoader(getActivity(), ContactsContract.Contacts.CONTENT_URI, PROJECTION, + null, null, ORDER); + } + + + /** + * Dislays either the name of the first contact or a message. + */ + @Override + public void onLoadFinished(Loader loader, Cursor cursor) { + + if (cursor != null) { + final int totalCount = cursor.getCount(); + if (totalCount > 0) { + cursor.moveToFirst(); + String name = cursor + .getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); + mMessageText.setText( + getResources().getString(R.string.contacts_string, totalCount, name)); + Log.d(TAG, "First contact loaded: " + name); + Log.d(TAG, "Total number of contacts: " + totalCount); + Log.d(TAG, "Total number of contacts: " + totalCount); + } else { + Log.d(TAG, "List of contacts is empty."); + mMessageText.setText(R.string.contacts_empty); + } + } + } + + @Override + public void onLoaderReset(Loader loader) { + mMessageText.setText(R.string.contacts_empty); + } + + /** + * Accesses the Contacts content provider directly to insert a new contact. + *

+ * The contact is called "__DUMMY ENTRY" and only contains a name. + */ + private void insertDummyContact() { + // Two operations are needed to insert a new contact. + ArrayList operations = new ArrayList(2); + + // First, set up a new raw contact. + ContentProviderOperation.Builder op = + ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null); + operations.add(op.build()); + + // Next, set the name for the contact. + op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, + DUMMY_CONTACT_NAME); + operations.add(op.build()); + + // Apply the operations. + ContentResolver resolver = getActivity().getContentResolver(); + try { + resolver.applyBatch(ContactsContract.AUTHORITY, operations); + } catch (RemoteException e) { + Log.d(TAG, "Could not add a new contact: " + e.getMessage()); + } catch (OperationApplicationException e) { + Log.d(TAG, "Could not add a new contact: " + e.getMessage()); + } + } +} diff --git a/sample-kotlin/src/main/res/layout/activity_main.xml b/sample-kotlin/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..b7274e3b --- /dev/null +++ b/sample-kotlin/src/main/res/layout/activity_main.xml @@ -0,0 +1,30 @@ + + +