diff --git a/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactDelegationExtraCommandHandler.java b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactDelegationExtraCommandHandler.java new file mode 100644 index 00000000..4358d39a --- /dev/null +++ b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactDelegationExtraCommandHandler.java @@ -0,0 +1,412 @@ +package com.google.androidbrowserhelper.trusted; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.browser.trusted.TrustedWebActivityCallbackRemote; +import androidx.core.content.FileProvider; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ContactDelegationExtraCommandHandler implements ExtraCommandHandler { + + private static final String TAG = "stomoki ContactDeleg"; + static final String COMMAND_CHECK_CONTACT_PERMISSION = "checkContactPermission"; + public static final String COMMAND_FETCH_CONTACTS = "fetchContacts"; + public static final String COMMAND_FETCH_CONTACT_ICON = "fetchContactIcon"; + + private static final String FOLDER_NAME = "contact_icons"; + + private static final String[] PROJECTION = { + ContactsContract.Contacts._ID, + ContactsContract.Contacts.LOOKUP_KEY, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, + }; + + private static final ExecutorService sExecutorService = Executors.newCachedThreadPool(); + + private String mProviderPackage; + private String mFileProviderAuthority; + + private File mDir; + + private synchronized void init(Context context) { + if (mProviderPackage != null) { + return; + } + + mDir = new File(context.getFilesDir(), FOLDER_NAME); + + if (!mDir.exists()) { + boolean mkDirSuccessful = mDir.mkdir(); + if (!mkDirSuccessful) { + Log.e(TAG, "Failed to create a directory for storing a splash image"); + } + } + + PackageManager packageManager = context.getPackageManager(); + + Log.d(TAG, "package = " + context.getPackageName()); + + TwaProviderPicker.Action action = TwaProviderPicker.pickProvider(packageManager); + mProviderPackage = action.provider; + + Log.d(TAG, "provider = " + mProviderPackage); + + ComponentName componentName = new ComponentName(context, LauncherActivity.class); + + try { + mFileProviderAuthority = + packageManager + .getActivityInfo(componentName, PackageManager.GET_META_DATA) + .metaData + .getString("android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY"); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + + Log.d(TAG, "authority = " + mFileProviderAuthority); + } + + @NonNull + @Override + public Bundle handleExtraCommand( + Context context, + String commandName, + Bundle args, + @Nullable TrustedWebActivityCallbackRemote callback) { + init(context); + + Bundle result = new Bundle(); + result.putBoolean(EXTRA_COMMAND_SUCCESS, false); + + Bundle callbackResult = new Bundle(); + + switch (commandName) { + case COMMAND_CHECK_CONTACT_PERMISSION: + Log.d(TAG, "COMMAND_CHECK_CONTACT_PERMISSION received"); + ContactPermissionRequestActivity.requestContactPermisson(context, callback); + result.putBoolean(EXTRA_COMMAND_SUCCESS, true); + break; + case COMMAND_FETCH_CONTACTS: + Log.d(TAG, "COMMAND_FETCH_CONTACTS received"); + boolean includeNames = args.getBoolean("includeNames"); + boolean includeEmails = args.getBoolean("includeEmails"); + boolean includeTel = args.getBoolean("includeTel"); + boolean includeAddresses = args.getBoolean("includeAddresses"); + + sExecutorService.submit( + () -> { + callbackResult.putParcelableArrayList( + "contacts", + getAllContacts( + context, includeNames, includeEmails, includeTel, + includeAddresses)); + try { + callback.runExtraCallback(COMMAND_FETCH_CONTACTS, callbackResult); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + result.putBoolean(EXTRA_COMMAND_SUCCESS, true); + break; + case COMMAND_FETCH_CONTACT_ICON: + Log.d(TAG, "COMMAND_FETCH_CONTACT_ICON received"); + + String id = args.getString("id"); + int iconSize = args.getInt("size"); + + sExecutorService.submit( + () -> { + Bitmap icon = getIcon(context, id, iconSize); + + if (icon != null) { + File file = new File(mDir, "contact_icon_" + id); + + Log.d(TAG, "file = " + file.getAbsolutePath()); + + try (FileOutputStream os = new FileOutputStream(file)) { + icon.compress(Bitmap.CompressFormat.PNG, 100, os); + os.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Log.d(TAG, "file written"); + + synchronized (context) { + Uri uri = FileProvider.getUriForFile(context, + mFileProviderAuthority, file); + + Log.d(TAG, "uri = " + uri); + + context.grantUriPermission( + mProviderPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + + callbackResult.putParcelable("icon", uri); + } + } + try { + callback.runExtraCallback(COMMAND_FETCH_CONTACT_ICON, callbackResult); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + + result.putBoolean(EXTRA_COMMAND_SUCCESS, true); + break; + default: + Log.d(TAG, "unknown command " + commandName); + break; + } + + return result; + } + + private Map> getDetails( + ContentResolver contentResolver, + Uri source, + String idColumn, + String dataColumn, + String sortOrder) { + Map> map = new HashMap<>(); + + Cursor cursor = contentResolver.query(source, null, null, null, sortOrder); + ArrayList list = new ArrayList<>(); + String key = ""; + String value; + + assert cursor != null; + + while (cursor.moveToNext()) { + String id = cursor.getString(cursor.getColumnIndexOrThrow(idColumn)); + value = cursor.getString(cursor.getColumnIndexOrThrow(dataColumn)); + if (value == null) { + value = ""; + } + if (key.isEmpty()) { + key = id; + list.add(value); + } else { + if (key.equals(id)) { + list.add(value); + } else { + map.put(key, list); + list = new ArrayList<>(); + list.add(value); + key = id; + } + } + } + map.put(key, list); + cursor.close(); + + return map; + } + + private Map> getAddressDetails(ContentResolver contentResolver) { + Map> map = new HashMap<>(); + + String addressSortOrder = + ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID + + " ASC, " + + ContactsContract.CommonDataKinds.StructuredPostal.DATA + + " ASC"; + Cursor cursor = + contentResolver.query( + ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI, + null, + null, + null, + addressSortOrder); + + assert cursor != null; + + ArrayList list = new ArrayList<>(); + String key = ""; + + while (cursor.moveToNext()) { + String id = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID)); + String city = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.CITY)); + String country = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)); + String formattedAddress = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS)); + String postcode = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)); + String region = + cursor.getString( + cursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.StructuredPostal.REGION)); + Bundle address = new Bundle(); + + address.putString("city", city); + address.putString("country", country); + address.putString("formattedAddress", formattedAddress); + address.putString("postcode", postcode); + address.putString("region", region); + + if (key.isEmpty()) { + key = id; + list.add(address); + } else { + if (key.equals(id)) { + list.add(address); + } else { + map.put(key, list); + list = new ArrayList<>(); + list.add(address); + key = id; + } + } + } + map.put(key, list); + cursor.close(); + + return map; + } + + public ArrayList getAllContacts( + Context context, + boolean includeNames, + boolean includeEmails, + boolean includeTel, + boolean includeAddresses) { + ContentResolver contentResolver = context.getContentResolver(); + + Map> emailMap = + includeEmails + ? getDetails( + contentResolver, + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + ContactsContract.CommonDataKinds.Email.CONTACT_ID, + ContactsContract.CommonDataKinds.Email.DATA, + ContactsContract.CommonDataKinds.Email.CONTACT_ID + + " ASC, " + + ContactsContract.CommonDataKinds.Email.DATA + + " ASC") + : null; + + Map> phoneMap = + includeTel + ? getDetails( + contentResolver, + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID, + ContactsContract.CommonDataKinds.Phone.DATA, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + + " ASC, " + + ContactsContract.CommonDataKinds.Phone.NUMBER + + " ASC") + : null; + + Map> addressMap = + includeAddresses ? getAddressDetails(contentResolver) : null; + + Log.d(TAG, "emailMap = " + emailMap); + Log.d(TAG, "phoneMap = " + phoneMap); + Log.d(TAG, "addressMap = " + addressMap); + + // A cursor containing the raw contacts data. + Cursor cursor = + contentResolver.query( + ContactsContract.Contacts.CONTENT_URI, + PROJECTION, + null, + null, + ContactsContract.Contacts.SORT_KEY_PRIMARY + " ASC"); + assert cursor != null; + if (!cursor.moveToFirst()) { + cursor.close(); + return new ArrayList<>(); + } + + ArrayList contacts = new ArrayList<>(cursor.getCount()); + do { + String id = cursor.getString( + cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID)); + String name = + cursor.getString( + cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)); + ArrayList email = emailMap != null ? emailMap.get(id) : null; + ArrayList tel = phoneMap != null ? phoneMap.get(id) : null; + ArrayList address = addressMap != null ? addressMap.get(id) : null; + + if (includeNames || email != null || tel != null || address != null) { + Bundle contact = new Bundle(); + + contact.putString("id", id); + contact.putString("name", name); + contact.putStringArrayList("email", email); + contact.putStringArrayList("tel", tel); + contact.putParcelableArrayList("address", address); + + contacts.add(contact); + } + } while (cursor.moveToNext()); + + cursor.close(); + return contacts; + } + + private Bitmap getIcon(Context context, String id, int iconSize) { + ContentResolver contentResolver = context.getContentResolver(); + + Uri contactUri = + ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(id)); + Uri photoUri = + Uri.withAppendedPath(contactUri, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); + Cursor cursor = + contentResolver.query( + photoUri, new String[]{ContactsContract.Contacts.Photo.PHOTO}, null, null, null); + if (cursor == null) { + return null; + } + try { + if (cursor.moveToFirst()) { + byte[] data = cursor.getBlob(0); + if (data != null) { + Bitmap icon = BitmapFactory.decodeStream(new ByteArrayInputStream(data)); + return iconSize > 0 ? Bitmap.createScaledBitmap(icon, iconSize, iconSize, true) + : icon; + } + } + } finally { + cursor.close(); + } + return null; + } +} diff --git a/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactPermissionRequestActivity.java b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactPermissionRequestActivity.java new file mode 100644 index 00000000..d56ed756 --- /dev/null +++ b/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/ContactPermissionRequestActivity.java @@ -0,0 +1,98 @@ +package com.google.androidbrowserhelper.trusted; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; +import androidx.browser.trusted.TrustedWebActivityCallbackRemote; +import androidx.core.app.ActivityCompat; + +public class ContactPermissionRequestActivity extends Activity { + + private static final String TAG = "stomoki ContactReq"; + + private static final String[] CONTACT_PERMISSION = {Manifest.permission.READ_CONTACTS}; + + private static final String CONTACT_PERMISSION_RESULT = "contactPermissionResult"; + + private static final String EXTRA_RESULT_RECEIVER = "EXTRA_RESULT_RECEIVER"; + private static final String EXTRA_PERMISSIONS = "EXTRA_PERMISSIONS"; + private static final String EXTRA_GRANT_RESULTS = "EXTRA_GRANTED_RESULT"; + + private Messenger mMessenger; + + public static void requestContactPermisson(Context context, + TrustedWebActivityCallbackRemote callback) { + Log.d(TAG, "Permission request started"); + + Handler handler = new Handler(Looper.getMainLooper(), message -> { + Bundle data = message.getData(); + Log.d(TAG, "Intent result: " + data); + + Bundle result = new Bundle(); + + result.putBoolean(CONTACT_PERMISSION_RESULT, false); + + String[] permissions = data.getStringArray(EXTRA_PERMISSIONS); + int[] grantedResult = data.getIntArray(EXTRA_GRANT_RESULTS); + + for (int i = 0; i < permissions.length; i++) { + if (permissions[i].equals(CONTACT_PERMISSION[0])) { + result.putBoolean(CONTACT_PERMISSION_RESULT, + grantedResult[i] == PackageManager.PERMISSION_GRANTED); + break; + } + } + + try { + callback.runExtraCallback( + ContactDelegationExtraCommandHandler.COMMAND_CHECK_CONTACT_PERMISSION, result); + } catch (RemoteException e) { + e.printStackTrace(); + } + + return true; + }); + + final Messenger messenger = new Messenger(handler); + Intent intent = new Intent(context, ContactPermissionRequestActivity.class); + intent.putExtra(EXTRA_PERMISSIONS, CONTACT_PERMISSION); + intent.putExtra(EXTRA_RESULT_RECEIVER, messenger); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String[] permissions = getIntent().getStringArrayExtra(EXTRA_PERMISSIONS); + mMessenger = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER); + ActivityCompat.requestPermissions(this, permissions, 0); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + Message message = new Message(); + Bundle data = new Bundle(); + data.putStringArray(EXTRA_PERMISSIONS, permissions); + data.putIntArray(EXTRA_GRANT_RESULTS, grantResults); + message.setData(data); + try { + mMessenger.send(message); + } catch (RemoteException e) { + e.printStackTrace(); + } finally { + finish(); + } + } +} diff --git a/demos/twa-contact-delegation/.gitignore b/demos/twa-contact-delegation/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/demos/twa-contact-delegation/.gitignore @@ -0,0 +1 @@ +/build diff --git a/demos/twa-contact-delegation/build.gradle b/demos/twa-contact-delegation/build.gradle new file mode 100644 index 00000000..35557f04 --- /dev/null +++ b/demos/twa-contact-delegation/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.google.androidbrowserhelper.demos.twa_contact_delegation' + + defaultConfig { + applicationId "com.google.androidbrowserhelper.demos.twa_contact_delegation" + minSdkVersion 21 + compileSdk 36 + targetSdkVersion 31 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(path: ':androidbrowserhelper') +} diff --git a/demos/twa-contact-delegation/src/main/AndroidManifest.xml b/demos/twa-contact-delegation/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2fa06fe5 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/AndroidManifest.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/twa-contact-delegation/src/main/java/com/google/androidbrowserhelper/demos/twa_contact_delegation/ContactDelegationService.java b/demos/twa-contact-delegation/src/main/java/com/google/androidbrowserhelper/demos/twa_contact_delegation/ContactDelegationService.java new file mode 100644 index 00000000..5dbb4d39 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/java/com/google/androidbrowserhelper/demos/twa_contact_delegation/ContactDelegationService.java @@ -0,0 +1,26 @@ +// Copyright 2019 Google Inc. All Rights Reserved. +// +// 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 com.google.androidbrowserhelper.demos.twa_contact_delegation; + + +import com.google.androidbrowserhelper.trusted.ContactDelegationExtraCommandHandler; +import com.google.androidbrowserhelper.trusted.DelegationService; + +public class ContactDelegationService extends DelegationService { + + public ContactDelegationService() { + registerExtraCommandHandler(new ContactDelegationExtraCommandHandler()); + } +} diff --git a/demos/twa-contact-delegation/src/main/res/drawable-anydpi/ic_notification_icon.xml b/demos/twa-contact-delegation/src/main/res/drawable-anydpi/ic_notification_icon.xml new file mode 100644 index 00000000..94b0a2ab --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/drawable-anydpi/ic_notification_icon.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/demos/twa-contact-delegation/src/main/res/drawable-hdpi/ic_notification_icon.png b/demos/twa-contact-delegation/src/main/res/drawable-hdpi/ic_notification_icon.png new file mode 100644 index 00000000..c44655ce Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/drawable-hdpi/ic_notification_icon.png differ diff --git a/demos/twa-contact-delegation/src/main/res/drawable-mdpi/ic_notification_icon.png b/demos/twa-contact-delegation/src/main/res/drawable-mdpi/ic_notification_icon.png new file mode 100644 index 00000000..241bdb8e Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/drawable-mdpi/ic_notification_icon.png differ diff --git a/demos/twa-contact-delegation/src/main/res/drawable-v24/ic_launcher_foreground.xml b/demos/twa-contact-delegation/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..c478e087 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/ic_notification_icon.png b/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/ic_notification_icon.png new file mode 100644 index 00000000..ba11f018 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/ic_notification_icon.png differ diff --git a/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/splash.png b/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 00000000..8de34211 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/drawable-xhdpi/splash.png differ diff --git a/demos/twa-contact-delegation/src/main/res/drawable-xxhdpi/ic_notification_icon.png b/demos/twa-contact-delegation/src/main/res/drawable-xxhdpi/ic_notification_icon.png new file mode 100644 index 00000000..e749e2f5 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/drawable-xxhdpi/ic_notification_icon.png differ diff --git a/demos/twa-contact-delegation/src/main/res/drawable/ic_launcher_background.xml b/demos/twa-contact-delegation/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..ab8bc42e --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6b78462d --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..287f5053 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a571e600 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..61da551c Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher.png b/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c41dd285 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher_round.png b/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..db5080a7 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher.png b/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..6dba46da Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..da31a871 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..15ac6817 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b216f2d3 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f25a4197 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..e96783cc Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/demos/twa-contact-delegation/src/main/res/raw/airhorn.mp3 b/demos/twa-contact-delegation/src/main/res/raw/airhorn.mp3 new file mode 100644 index 00000000..af4d1866 Binary files /dev/null and b/demos/twa-contact-delegation/src/main/res/raw/airhorn.mp3 differ diff --git a/demos/twa-contact-delegation/src/main/res/values/colors.xml b/demos/twa-contact-delegation/src/main/res/values/colors.xml new file mode 100644 index 00000000..77536b23 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #303F9F + #303F9F + #303F9F + #303F9F + #303F9F + #303F9F + diff --git a/demos/twa-contact-delegation/src/main/res/values/strings.xml b/demos/twa-contact-delegation/src/main/res/values/strings.xml new file mode 100644 index 00000000..17fce64d --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + + twa-contact-delegation + + [{ + \"relation\": [\"delegate_permission/common.handle_all_urls\"], + \"target\": { + \"namespace\": \"web\", + \"site\": \"https://whatpwacando.today/\"} + }] + + com.google.browser.examples.twa_contact_delegation.fileprovider + diff --git a/demos/twa-contact-delegation/src/main/res/xml/filepaths.xml b/demos/twa-contact-delegation/src/main/res/xml/filepaths.xml new file mode 100644 index 00000000..bda6da09 --- /dev/null +++ b/demos/twa-contact-delegation/src/main/res/xml/filepaths.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle index 48798a7f..0c7f4811 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,6 +40,7 @@ include ':demos:custom-tabs-ephemeral-with-fallback' include ':demos:custom-tabs-ephemeral' include ':demos:custom-tabs-session' include ':demos:twa-basic' +include ':demos:twa-contact-delegation' include ':demos:twa-custom-launcher' include ':demos:twa-firebase-analytics' include ':demos:twa-location-delegation'