diff options
author | Xin Li <delphij@google.com> | 2019-09-05 16:53:21 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-09-05 16:53:21 +0000 |
commit | 2b151947b067c5e959fcad1bbdfe47fd61902069 (patch) | |
tree | c24e107fad8d9af38ddf10d376f337969fdc01f6 | |
parent | 48d3e658a387e158e6111d35f274696054a7f403 (diff) | |
parent | 5b2bb6743e06ffc5141237073518387b149da039 (diff) | |
download | android-2b151947b067c5e959fcad1bbdfe47fd61902069.tar.gz |
Merge "DO NOT MERGE - Merge Android 10 into master"ndk-sysroot-r21
188 files changed, 6656 insertions, 54 deletions
diff --git a/content/SharingShortcuts/Application/.gitignore b/content/SharingShortcuts/Application/.gitignore new file mode 100644 index 00000000..6eb878d4 --- /dev/null +++ b/content/SharingShortcuts/Application/.gitignore @@ -0,0 +1,16 @@ +# Copyright 2013 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. +src/template/ +src/common/ +build.gradle diff --git a/content/SharingShortcuts/Application/proguard-project.txt b/content/SharingShortcuts/Application/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/content/SharingShortcuts/Application/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java b/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java new file mode 100644 index 00000000..0a5bd704 --- /dev/null +++ b/content/SharingShortcuts/Application/src/androidTest/java/com/example/android/sharingshortcuts/test/SampleTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Tests for DirectShare sample. + */ +public class SampleTest extends ActivityInstrumentationTestCase2<MainActivity> { + + private MainActivity mTestActivity; + + public SampleTest() { + super(MainActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mTestActivity = getActivity(); + } + + /** + * Test if the test fixture has been set up correctly. + */ + public void testPreconditions() { + assertNotNull("mTestActivity is null", mTestActivity); + } + +} diff --git a/content/SharingShortcuts/Application/src/main/AndroidManifest.xml b/content/SharingShortcuts/Application/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d03e7c9d --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/AndroidManifest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- + Copyright 2019 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.example.android.sharingshortcuts" + android:versionCode="1" + android:versionName="1.0"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:theme="@style/SharingShortcutsTheme" + tools:ignore="GoogleAppIndexingWarning"> + + <activity + android:name=".MainActivity" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <!-- Reference resource file where the app's shortcuts are defined --> + <meta-data + android:name="android.app.shortcuts" + android:resource="@xml/shortcuts" /> + </activity> + + <activity + android:name=".SendMessageActivity" + android:label="@string/app_name" + android:theme="@style/SharingShortcutsDialogTheme"> + <!-- This activity can respond to Intents of type SEND and with text/plain data --> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="text/plain" /> + </intent-filter> + <!-- Only needed if you import the sharetarget AndroidX library that provides + backwards compatibility with the old DirectShare API. + The activity that receives the Sharing Shortcut intent needs to be taken into + account with this chooser target provider. --> + <meta-data + android:name="android.service.chooser.chooser_target_service" + android:value="androidx.sharetarget.ChooserTargetServiceCompat" /> + </activity> + + <activity + android:name=".SelectContactActivity" + android:label="@string/app_name" + android:theme="@style/SharingShortcutsDialogTheme" /> + + <!-- Only needed if you want to add a thumbnail to the direct share. + FileProvider is a subclass of ContentProvider that facilitates secure sharing. + Here we specify a FileProvider for our app. --> + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="com.example.android.sharingshortcuts.fileprovider" + android:exported="false" + android:grantUriPermissions="true"> + <!-- Specify the directories the FileProvider can generate content URIs for. --> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + + </application> + +</manifest> diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java new file mode 100644 index 00000000..155a8412 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/Contact.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import androidx.annotation.NonNull; + +/** + * Provides the list of dummy contacts. This sample implements this as constants, but real-life apps + * should use a database and such. + */ +public class Contact { + + /** + * The list of dummy contacts. + */ + public static final Contact[] CONTACTS = { + new Contact("Tereasa"), + new Contact("Chang"), + new Contact("Kory"), + new Contact("Clare"), + new Contact("Landon"), + new Contact("Kyle"), + new Contact("Deana"), + new Contact("Daria"), + new Contact("Melisa"), + new Contact("Sammie"), + }; + + /** + * The contact ID. + */ + public static final String ID = "contact_id"; + + /** + * Representative invalid contact ID. + */ + public static final int INVALID_ID = -1; + + /** + * The name of this contact. + */ + private final String mName; + + /** + * Instantiates a new {@link Contact}. + * + * @param name The name of the contact. + */ + public Contact(@NonNull String name) { + mName = name; + } + + /** + * Finds a {@link Contact} specified by a contact ID. + * + * @param id The contact ID. This needs to be a valid ID. + * @return A {@link Contact} + */ + public static Contact byId(int id) { + return CONTACTS[id]; + } + + /** + * Gets the name of this contact. + * + * @return The name of this contact. + */ + public String getName() { + return mName; + } + + /** + * Gets the icon of this contact. + * + * @return The icon. + */ + public int getIcon() { + return R.mipmap.logo_avatar; + } + +} diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java new file mode 100644 index 00000000..86925922 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/ContactViewBinder.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.widget.TextView; + +/** + * A simple utility to bind a {@link TextView} with a {@link Contact}. + */ +public class ContactViewBinder { + + /** + * Binds the {@code textView} with the specified {@code contact}. + * + * @param contact The contact. + * @param textView The TextView. + */ + public static void bind(Contact contact, TextView textView) { + textView.setText(contact.getName()); + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(contact.getIcon(), 0, 0, 0); + } + +} diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java new file mode 100644 index 00000000..17dfdc9f --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/MainActivity.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.app.Activity; +import android.content.ClipData; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; + +import androidx.core.content.FileProvider; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Provides the landing screen of this sample. There is nothing particularly interesting here. All + * the codes related to the Direct Share feature are in {@link SharingShortcutsManager}. + */ +public class MainActivity extends Activity { + + // Domain authority for our app FileProvider + private static final String FILE_PROVIDER_AUTHORITY = + "com.example.android.sharingshortcuts.fileprovider"; + + // Cache directory to store images + // This is the same path specified in the @xml/file_paths and accessed from the AndroidManifest + private static final String IMAGE_CACHE_DIR = "images"; + + // Name of the file to use for the thumbnail image + private static final String IMAGE_FILE = "image.png"; + + private EditText mEditBody; + private SharingShortcutsManager mSharingShortcutsManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + mEditBody = findViewById(R.id.body); + findViewById(R.id.share).setOnClickListener(mOnClickListener); + + mSharingShortcutsManager = new SharingShortcutsManager(); + mSharingShortcutsManager.pushDirectShareTargets(this); + } + + private View.OnClickListener mOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.share: + share(); + break; + } + } + }; + + /** + * Emits a sample share {@link Intent}. + */ + private void share() { + Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); + sharingIntent.setType("text/plain"); + sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, mEditBody.getText().toString()); + // (Optional) If you want a preview title, set it with Intent.EXTRA_TITLE + sharingIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.send_intent_title)); + + // (Optional) if you want a preview thumbnail, create a content URI and add it + // The system only supports content URIs + ClipData thumbnail = getClipDataThumbnail(); + if (thumbnail != null) { + sharingIntent.setClipData(thumbnail); + sharingIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + + startActivity(Intent.createChooser(sharingIntent, null)); + } + + /** + * Get ClipData thumbnail object that needs to be passed in the Intent. + * It stores the launcher icon in the cache and retrieves in a content URI. + * The ClipData object is created with the URI we get from the FileProvider. + * <p> + * For this to work, you need to configure a FileProvider in the project. We added it to the + * AndroidManifest.xml file where we can configure it. We added the images path where we + * save the image to the @xml/file_paths file which tells the FileProvider where we intend to + * request content URIs. + * + * @return thumbnail ClipData object to set in the sharing Intent. + */ + private ClipData getClipDataThumbnail() { + try { + Uri contentUri = saveImageThumbnail(); + return ClipData.newUri(getContentResolver(), null, contentUri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Save our Launcher image to the cache and return it as a content URI. + * + * IMPORTANT: This could trigger StrictMode. Do not do this in your app. + * For the simplicity of the code sample, this is running on the Main thread + * but these tasks should be done in a background thread. + * + * @throws IOException if image couldn't be saved to the cache. + * @return image content Uri + */ + private Uri saveImageThumbnail() throws IOException { + Bitmap bm = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); + File cachePath = new File(getCacheDir(), IMAGE_CACHE_DIR); + cachePath.mkdirs(); + FileOutputStream stream = new FileOutputStream(cachePath + "/" + IMAGE_FILE); + bm.compress(Bitmap.CompressFormat.PNG, 100, stream); + stream.close(); + File imagePath = new File(getCacheDir(), IMAGE_CACHE_DIR); + File newFile = new File(imagePath, IMAGE_FILE); + return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, newFile); + } +} diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java new file mode 100644 index 00000000..4e4d5437 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SelectContactActivity.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * The dialog for selecting a contact to share the text with. This dialog is shown when the user + * taps on this sample's icon rather than any of the Direct Share contacts. + */ +public class SelectContactActivity extends Activity { + + /** + * The action string for Intents. + */ + public static final String ACTION_SELECT_CONTACT = + "com.example.android.sharingshortcuts.intent.action.SELECT_CONTACT"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_select_contact); + Intent intent = getIntent(); + if (!ACTION_SELECT_CONTACT.equals(intent.getAction())) { + finish(); + return; + } + // Set up the list of contacts + RecyclerView recyclerView = findViewById(R.id.recycler_view); + recyclerView.setAdapter(mContactAdapter); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + } + + private final RecyclerView.Adapter mContactAdapter = + new RecyclerView.Adapter<ContactViewHolder>() { + + @NonNull + @Override + public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, + int viewType) { + TextView textView = (TextView) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_contact, parent, false); + return new ContactViewHolder(textView); + } + + @Override + public void onBindViewHolder(@NonNull ContactViewHolder holder, + final int position) { + Contact contact = Contact.CONTACTS[position]; + ContactViewBinder.bind(contact, (TextView) holder.itemView); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent data = new Intent(); + data.putExtra(Contact.ID, position); + setResult(RESULT_OK, data); + finish(); + } + }); + } + + @Override + public int getItemCount() { + return Contact.CONTACTS.length; + } + }; + + private static class ContactViewHolder extends RecyclerView.ViewHolder { + + ContactViewHolder(@NonNull TextView textView) { + super(textView); + } + } +} diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java new file mode 100644 index 00000000..7519a7b8 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SendMessageActivity.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +/** + * Provides the UI for sharing a text with a {@link Contact}. + */ +public class SendMessageActivity extends Activity { + + /** + * The request code for {@link SelectContactActivity}. This is used when the user doesn't + * select any of Direct Share icons. + */ + private static final int REQUEST_SELECT_CONTACT = 1; + + /** + * The text to share. + */ + private String mBody; + + /** + * The ID of the contact to share the text with. + */ + private int mContactId; + + // View references. + private TextView mTextContactName; + private TextView mTextMessageBody; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_send_message); + setTitle(R.string.sending_message); + // View references. + mTextContactName = findViewById(R.id.contact_name); + mTextMessageBody = findViewById(R.id.message_body); + // Handle the share Intent. + boolean handled = handleIntent(getIntent()); + if (!handled) { + finish(); + return; + } + // Bind event handlers. + findViewById(R.id.send).setOnClickListener(mOnClickListener); + // Set up the UI. + prepareUi(); + // The contact ID will not be passed on when the user clicks on the app icon rather than any + // of the Direct Share icons. In this case, we show another dialog for selecting a contact. + if (mContactId == Contact.INVALID_ID) { + selectContact(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_SELECT_CONTACT: + if (resultCode == RESULT_OK) { + mContactId = data.getIntExtra(Contact.ID, Contact.INVALID_ID); + } + // Give up sharing the send_message if the user didn't choose a contact. + if (mContactId == Contact.INVALID_ID) { + finish(); + return; + } + prepareUi(); + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** + * Handles the passed {@link Intent}. This method can only handle intents for sharing a plain + * text. {@link #mBody} and {@link #mContactId} are modified accordingly. + * + * @param intent The {@link Intent}. + * @return true if the {@code intent} is handled properly. + */ + private boolean handleIntent(Intent intent) { + if (Intent.ACTION_SEND.equals(intent.getAction()) + && "text/plain".equals(intent.getType())) { + mBody = intent.getStringExtra(Intent.EXTRA_TEXT); + // The intent comes from Direct share + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) { + String shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID); + mContactId = Integer.valueOf(shortcutId); + } else { + // The text was shared and the user chose our app + mContactId = Contact.INVALID_ID; + } + return true; + } + return false; + } + + /** + * Sets up the UI. + */ + private void prepareUi() { + if (mContactId != Contact.INVALID_ID) { + Contact contact = Contact.byId(mContactId); + ContactViewBinder.bind(contact, mTextContactName); + } + mTextMessageBody.setText(mBody); + } + + /** + * Delegates selection of a {@Contact} to {@link SelectContactActivity}. + */ + private void selectContact() { + Intent intent = new Intent(this, SelectContactActivity.class); + intent.setAction(SelectContactActivity.ACTION_SELECT_CONTACT); + startActivityForResult(intent, REQUEST_SELECT_CONTACT); + } + + private View.OnClickListener mOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.send: + send(); + break; + } + } + }; + + /** + * Pretends to send the text to the contact. This only shows a dummy message. + */ + private void send() { + Toast.makeText(this, + getString(R.string.message_sent, mBody, Contact.byId(mContactId).getName()), + Toast.LENGTH_SHORT).show(); + finish(); + } + +} diff --git a/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java new file mode 100644 index 00000000..14cc9290 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/java/com/example/android/sharingshortcuts/SharingShortcutsManager.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 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 com.example.android.sharingshortcuts; + +import android.content.Context; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Provides the Sharing Shortcuts items to the system. + * <p> + * Use the ShortcutManagerCompat to make it work on older Android versions + * without any extra work needed. + * <p> + * Interactions with the ShortcutManager API can happen on any thread. + */ +public class SharingShortcutsManager { + + /** + * Define maximum number of shortcuts. + * Don't add more than {@link ShortcutManagerCompat#getMaxShortcutCountPerActivity(Context)}. + */ + private static final int MAX_SHORTCUTS = 4; + + /** + * Category name defined in res/xml/shortcuts.xml that accepts data of type text/plain + * and will trigger {@link SendMessageActivity} + */ + private static final String CATEGORY_TEXT_SHARE_TARGET = + "com.example.android.sharingshortcuts.category.TEXT_SHARE_TARGET"; + + /** + * Publish the list of dynamics shortcuts that will be used in Direct Share. + * <p> + * For each shortcut, we specify the categories that it will be associated to, + * the intent that will trigger when opened as a static launcher shortcut, + * and the Shortcut ID between other things. + * <p> + * The Shortcut ID that we specify in the {@link ShortcutInfoCompat.Builder} constructor will + * be received in the intent as {@link Intent#EXTRA_SHORTCUT_ID}. + * <p> + * In this code sample, this method is completely static. We are always setting the same sharing + * shortcuts. In a real-world example, we would replace existing shortcuts depending on + * how the user interacts with the app as often as we want to. + */ + public void pushDirectShareTargets(@NonNull Context context) { + ArrayList<ShortcutInfoCompat> shortcuts = new ArrayList<>(); + + // Category that our sharing shortcuts will be assigned to + Set<String> contactCategories = new HashSet<>(); + contactCategories.add(CATEGORY_TEXT_SHARE_TARGET); + + // Adding maximum number of shortcuts to the list + for (int id = 0; id < MAX_SHORTCUTS; ++id) { + Contact contact = Contact.byId(id); + + // Item that will be sent if the shortcut is opened as a static launcher shortcut + Intent staticLauncherShortcutIntent = new Intent(Intent.ACTION_DEFAULT); + + // Creates a new Sharing Shortcut and adds it to the list + // The id passed in the constructor will become EXTRA_SHORTCUT_ID in the received Intent + shortcuts.add(new ShortcutInfoCompat.Builder(context, Integer.toString(id)) + .setShortLabel(contact.getName()) + // Icon that will be displayed in the share target + .setIcon(IconCompat.createWithResource(context, contact.getIcon())) + .setIntent(staticLauncherShortcutIntent) + // Make this sharing shortcut cached by the system + // Even if it is unpublished, it can still appear on the sharesheet + .setLongLived() + .setCategories(contactCategories) + // Person objects are used to give better suggestions + .setPerson(new Person.Builder() + .setName(contact.getName()) + .build()) + .build()); + } + + ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts); + } + + /** + * Remove all dynamic shortcuts + */ + public void removeAllDirectShareTargets(@NonNull Context context) { + ShortcutManagerCompat.removeAllDynamicShortcuts(context); + } +} diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..524303d8 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_main.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_medium" + android:text="@string/explanation" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@android:color/darker_gray" /> + + <EditText + android:id="@+id/body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/margin_medium" + android:layout_marginTop="@dimen/margin_medium" + android:layout_marginEnd="@dimen/margin_medium" + android:hint="@string/text_to_share" + android:text="@string/hello" /> + + <Button + android:id="@+id/share" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:layout_marginStart="@dimen/margin_medium" + android:layout_marginEnd="@dimen/margin_medium" + android:text="@string/share" /> + +</LinearLayout>
\ No newline at end of file diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml new file mode 100644 index 00000000..b9cd8032 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_select_contact.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/recycler_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/margin_medium" + android:clipToPadding="false" + android:divider="@null" + android:paddingTop="@dimen/margin_tiny" + android:paddingBottom="@dimen/margin_tiny" /> diff --git a/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml b/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml new file mode 100644 index 00000000..b8ad5957 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/layout/activity_send_message.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/dialog_padding" + android:layout_marginTop="@dimen/dialog_padding" + android:layout_marginEnd="@dimen/dialog_padding" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:text="@string/to" + android:textAppearance="@android:style/TextAppearance.Material.Caption" /> + + <TextView + android:id="@+id/contact_name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/margin_medium" + android:layout_weight="1" + android:drawablePadding="@dimen/margin_medium" + android:gravity="center_vertical" + android:textAppearance="@android:style/TextAppearance.Material.Body1" + tools:drawableStart="@mipmap/logo_avatar" + tools:text="Taro" /> + + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/dialog_padding" + android:layout_marginTop="@dimen/margin_medium" + android:layout_marginEnd="@dimen/dialog_padding" + android:gravity="center_vertical" + android:text="@string/body" + android:textAppearance="@android:style/TextAppearance.Material.Caption" /> + + <TextView + android:id="@+id/message_body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/dialog_padding" + android:layout_marginEnd="@dimen/dialog_padding" + android:gravity="top" + android:hint="@string/hint_body" + android:paddingTop="@dimen/margin_small" + android:paddingBottom="@dimen/margin_small" + android:textAppearance="@android:style/TextAppearance.Material.Body1" + tools:text="Hello, world!" /> + + <Button + android:id="@+id/send" + style="@android:style/Widget.Material.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:layout_marginEnd="@dimen/dialog_button_padding" + android:layout_marginBottom="@dimen/dialog_button_padding" + android:text="@string/send" /> + +</LinearLayout> diff --git a/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml b/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml new file mode 100644 index 00000000..a7c17354 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/layout/item_contact.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/contact_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:drawablePadding="@dimen/margin_medium" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingTop="@dimen/margin_tiny" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingBottom="@dimen/margin_tiny" + tools:drawableStart="@mipmap/logo_avatar" + tools:text="Taro" /> diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..988f2ec8 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png Binary files differnew file mode 100644 index 00000000..8892c08d --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-hdpi/logo_avatar.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..0baa1cc7 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png Binary files differnew file mode 100644 index 00000000..c2de7747 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-mdpi/logo_avatar.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..da0aa2f2 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png Binary files differnew file mode 100644 index 00000000..10c2dc9a --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xhdpi/logo_avatar.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..e1cc1ff1 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png Binary files differnew file mode 100644 index 00000000..df02f04f --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxhdpi/logo_avatar.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..8b0f60c4 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png Binary files differnew file mode 100644 index 00000000..dc8d3769 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/mipmap-xxxhdpi/logo_avatar.png diff --git a/content/SharingShortcuts/Application/src/main/res/values/colors.xml b/content/SharingShortcuts/Application/src/main/res/values/colors.xml new file mode 100644 index 00000000..4ffc20b8 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/values/colors.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<resources> + <color name="primary">#3F51B5</color> + <color name="primary_dark">#303F9F</color> + <color name="primary_light">#C5CAE9</color> + <color name="accent">#00BCD4</color> + <color name="primary_text">#212121</color> + <color name="secondary_text">#727272</color> + <color name="icons">#FFFFFF</color> + <color name="divider">#B6B6B6</color> +</resources> diff --git a/content/SharingShortcuts/Application/src/main/res/values/dimens.xml b/content/SharingShortcuts/Application/src/main/res/values/dimens.xml new file mode 100644 index 00000000..dce82532 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/values/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<resources> + <dimen name="dialog_padding">24dp</dimen> + <dimen name="dialog_button_padding">8dp</dimen> +</resources>
\ No newline at end of file diff --git a/content/SharingShortcuts/Application/src/main/res/values/strings.xml b/content/SharingShortcuts/Application/src/main/res/values/strings.xml new file mode 100644 index 00000000..2fd9ffa1 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/values/strings.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<resources> + + <!-- MainActivity --> + + <string name="send_intent_title">Send message</string> + <string name="explanation"> + This app demonstrates how to implement Direct Share. Use some other app and share a text. + For your convenience, you can also use the input below to share the text. + </string> + <string name="text_to_share">Text to share</string> + <string name="hello">Hello!</string> + <string name="share">Share</string> + + <!-- SendMessageActivity --> + + <string name="sending_message">Sending a message</string> + <string name="to">To:</string> + <string name="send">Send</string> + <string name="body">Body:</string> + <string name="hint_body">Edit your message</string> + <string name="message_sent">Sent a message \"%1$s\" to %2$s</string> + +</resources>
\ No newline at end of file diff --git a/content/SharingShortcuts/Application/src/main/res/values/styles.xml b/content/SharingShortcuts/Application/src/main/res/values/styles.xml new file mode 100644 index 00000000..ca1a0e7b --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/values/styles.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<resources> + + <style name="SharingShortcutsTheme" parent="android:Theme.Material.Light.DarkActionBar"> + <item name="android:colorPrimary">@color/primary</item> + <item name="android:colorPrimaryDark">@color/primary_dark</item> + <item name="android:colorAccent">@color/accent</item> + </style> + + <style name="SharingShortcutsDialogTheme" parent="android:Theme.Material.Light.Dialog"> + <item name="android:colorPrimary">@color/primary</item> + <item name="android:colorPrimaryDark">@color/primary_dark</item> + <item name="android:colorAccent">@color/accent</item> + </style> + + +</resources>
\ No newline at end of file diff --git a/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml b/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..5d344b31 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/xml/file_paths.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<paths> + <cache-path + name="shared_images" + path="images/" /> +</paths> diff --git a/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml b/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml new file mode 100644 index 00000000..a715cd35 --- /dev/null +++ b/content/SharingShortcuts/Application/src/main/res/xml/shortcuts.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 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. + --> +<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> + <share-target android:targetClass="com.example.android.sharingshortcuts.SendMessageActivity"> + <data android:mimeType="text/plain" /> + <category android:name="com.example.android.sharingshortcuts.category.TEXT_SHARE_TARGET" /> + </share-target> +</shortcuts>
\ No newline at end of file diff --git a/content/SharingShortcuts/build.gradle b/content/SharingShortcuts/build.gradle new file mode 100644 index 00000000..9fa56856 --- /dev/null +++ b/content/SharingShortcuts/build.gradle @@ -0,0 +1,21 @@ + + +// BEGIN_EXCLUDE +import com.example.android.samples.build.SampleGenPlugin +apply plugin: SampleGenPlugin + +samplegen { + pathToBuild "../../../../build" + pathToSamplesCommon "../../common" +} +apply from: "../../../../build/build.gradle" +buildscript { + ext.kotlin_version = '1.3.21' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} +// END_EXCLUDE diff --git a/content/SharingShortcuts/buildSrc/build.gradle b/content/SharingShortcuts/buildSrc/build.gradle new file mode 100644 index 00000000..75b00eeb --- /dev/null +++ b/content/SharingShortcuts/buildSrc/build.gradle @@ -0,0 +1,17 @@ + +repositories { + google() + jcenter() +} +dependencies { + implementation 'org.freemarker:freemarker:2.3.20' +} + +sourceSets { + main { + groovy { + srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy") + } + } +} + diff --git a/content/SharingShortcuts/gradle.properties b/content/SharingShortcuts/gradle.properties new file mode 100644 index 00000000..94f84728 --- /dev/null +++ b/content/SharingShortcuts/gradle.properties @@ -0,0 +1,22 @@ + +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useAndroidX=true +android.enableJetifier=true diff --git a/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 00000000..8c0fb64a --- /dev/null +++ b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.jar diff --git a/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..77cff654 --- /dev/null +++ b/content/SharingShortcuts/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Feb 18 15:19:26 CET 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/content/SharingShortcuts/gradlew b/content/SharingShortcuts/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/content/SharingShortcuts/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/content/SharingShortcuts/gradlew.bat b/content/SharingShortcuts/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/content/SharingShortcuts/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/content/SharingShortcuts/screenshots/1-main.png b/content/SharingShortcuts/screenshots/1-main.png Binary files differnew file mode 100644 index 00000000..693b2be3 --- /dev/null +++ b/content/SharingShortcuts/screenshots/1-main.png diff --git a/content/SharingShortcuts/screenshots/2-intent.png b/content/SharingShortcuts/screenshots/2-intent.png Binary files differnew file mode 100644 index 00000000..f01e3951 --- /dev/null +++ b/content/SharingShortcuts/screenshots/2-intent.png diff --git a/content/SharingShortcuts/screenshots/3-message.png b/content/SharingShortcuts/screenshots/3-message.png Binary files differnew file mode 100644 index 00000000..cde24969 --- /dev/null +++ b/content/SharingShortcuts/screenshots/3-message.png diff --git a/content/SharingShortcuts/screenshots/4-static_shortcuts.png b/content/SharingShortcuts/screenshots/4-static_shortcuts.png Binary files differnew file mode 100644 index 00000000..0404027b --- /dev/null +++ b/content/SharingShortcuts/screenshots/4-static_shortcuts.png diff --git a/content/SharingShortcuts/screenshots/icon-web.png b/content/SharingShortcuts/screenshots/icon-web.png Binary files differnew file mode 100644 index 00000000..ee7c5579 --- /dev/null +++ b/content/SharingShortcuts/screenshots/icon-web.png diff --git a/content/SharingShortcuts/settings.gradle b/content/SharingShortcuts/settings.gradle new file mode 100644 index 00000000..0a5c310b --- /dev/null +++ b/content/SharingShortcuts/settings.gradle @@ -0,0 +1,2 @@ + +include 'Application' diff --git a/content/SharingShortcuts/template-params.xml b/content/SharingShortcuts/template-params.xml new file mode 100644 index 00000000..5bf56268 --- /dev/null +++ b/content/SharingShortcuts/template-params.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- + Copyright 2013 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. +--> + +<sample> + <name>SharingShortcuts</name> + <group>Content</group> + <package>com.example.android.sharingshortcuts</package> + + <minSdk>21</minSdk> + + <!-- Include additional dependencies here.--> + <dependency>androidx.appcompat:appcompat:1.1.0-alpha03</dependency> + <dependency>androidx.sharetarget:sharetarget:1.0.0-alpha01</dependency> + <dependency>androidx.recyclerview:recyclerview:1.0.0</dependency> + + <strings> + <intro> + <![CDATA[ +This sample demonstrates how to provide the Sharing Shortcuts - Direct Share feature. The app shows +some options directly in the list of share intent candidates and launcher shortcuts. + ]]> + </intro> + </strings> + + <template src="base" /> + <androidX>true</androidX> + + <metadata> + <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} --> + <status>DRAFT</status> + <categories>Content</categories> + <technologies>Android</technologies> + <languages>Java</languages> + <solutions>Mobile</solutions> + <level>INTERMEDIATE</level> + <icon>screenshots/icon-web.png</icon> + <screenshots> + <img>screenshots/1-main.png</img> + <img>screenshots/2-intent.png</img> + <img>screenshots/3-message.png</img> + <img>screenshots/4-static_shortcuts.png</img> + </screenshots> + + <api_refs> + <androidx>androidx.core.content.pm.ShortcutManagerCompat</androidx> + <androidx>androidx.core.content.pm.ShortcutInfoCompat</androidx> + </api_refs> + <description> + <![CDATA[ +Sample demonstrating how to show some options directly in the list of share intent candidates. + ]]> + </description> + + <intro> + <![CDATA[ +Direct Share is a feature that allows apps to show their internal options directly in the +system Intent chooser dialog. This sample is a dummy messaging app, and just like any other +messaging apps, it receives intents for sharing a plain text. When a user shares some text from some +other app, this sample app will be listed as an option. Using the Direct Share feature, this app +also shows some of contacts directly in the chooser dialog. + +Direct Share was [first introduced][1] in Android M where you had to implement a service to provide +direct share targets on demand. The way to do that changed in Android Q, you need to provide your +direct share targets in advance. First, you have to declare share-target elements in the same +application's resource file than [static shortcuts][2]. Then, you need to publish dynamic +shortcuts with the same category you declared in the share-target with the [ShortcutManager API][3]. +You will need to manually update the list of shortcuts every time you consider it appropriate. +The API offers methods to update, remove or add shortcuts. You can use the +[ShortcutInfoCompat.Builder][4] to customize your shortcut. If you don't want to block the UI thread +doing these operations, interactions with the ShortcutManager can also happen on a background thread. + +There are three ways the app is sending/receiving intents: +- Dynamic Share: The user selected the app in the sharesheet to receive the text. After this, +the user will have to select the contact to share the text with. In our case, the app receives an +intent of type Intent.ACTION_SEND. +- Direct Share: The user selected a person of your app in the sharesheet to share the text with. +The received intent of type Intent.ACTION_SEND will contain a String EXTRA_SHORTCUT_ID that will +have the id of the shortcut that was selected. After this, the app is ready to send the text. +- Launcher shortcut: When the user taps on a launcher shortcut, the intent that was +added to the shortcut will get fired. In our case, it triggers an intent of type Intent.ACTION_DEFAULT. + +To make Direct Share backwards compatible with older Android versions, you need to add the AndroidX +sharetarget library and in your AndroidManifest.xml, add a meta-data tag in your Activity that +receives the Intent. Specify android:name as android.service.chooser.chooser_target_service and +android:value as androidx.sharetarget.ChooserTargetServiceCompat. + +The way to share text has also changed. Before, you could specify a title in the +[Intent.createChooser()][5] method itself. That is deprecated and unused in Android Q. You can achieve +the same behavior by adding an Intent.EXTRA_TITLE extra to the intent. Similarly, if you want a +preview thumbnail to appear, you can create a content URI and set a ClipData object in the intent. +You can see how to do that in our example, open the MainActivity.java file for more details. + +[1]: https://developer.android.com/about/versions/marshmallow/android-6.0#direct-share +[2]: https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts +[3]: https://developer.android.com/reference/androidx/core/content/pm/ShortcutManagerCompat.html +[4]: https://developer.android.com/reference/androidx/core/content/pm/ShortcutInfoCompat.Builder.html +[5]: https://developer.android.com/reference/android/content/Intent.html#createChooser(android.content.Intent,%20java.lang.CharSequence) + ]]> + </intro> + </metadata> +</sample> diff --git a/media/MediaBrowserService/template-params.xml b/media/MediaBrowserService/template-params.xml index 9abe2120..b7f6f722 100644 --- a/media/MediaBrowserService/template-params.xml +++ b/media/MediaBrowserService/template-params.xml @@ -29,11 +29,7 @@ <strings> <intro> - <![CDATA[ -This sample shows how to implement an audio media app that provides -media library metadata and playback controls through a standard -service. It exposes a simple music library through the new -MediaBrowserService and provides MediaSession callbacks.]]> + <![CDATA[ This sample is DEPRECATED. ]]> </intro> </strings> @@ -42,7 +38,7 @@ MediaBrowserService and provides MediaSession callbacks.]]> <template src="base-application" /> <metadata> - <status>PUBLISHED</status> + <status>DEPRECATED</status> <categories>Media</categories> <technologies>Android</technologies> <languages>Java</languages> @@ -62,6 +58,10 @@ MediaBrowserService and provides MediaSession callbacks.]]> </api_refs> <description> <![CDATA[ +This sample is deprecated. See the +[Universal Android Music Player](https://github.com/googlesamples/android-UniversalMusicPlayer) +sample for a more complete solution for using a MediaSession and MediaBrowserService. + This sample shows how to implement a media app that allows background playback of audio, and provide a media library that is exposed to other apps. diff --git a/notification/Bubbles/.gitignore b/notification/Bubbles/.gitignore new file mode 100644 index 00000000..603b1407 --- /dev/null +++ b/notification/Bubbles/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/notification/Bubbles/app/.gitignore b/notification/Bubbles/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/notification/Bubbles/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/notification/Bubbles/app/build.gradle b/notification/Bubbles/app/build.gradle new file mode 100644 index 00000000..1600ee47 --- /dev/null +++ b/notification/Bubbles/app/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 'android-Q' + defaultConfig { + applicationId "com.example.android.bubbles" + minSdkVersion 'Q' + targetSdkVersion 'Q' + versionCode 1 + versionName '1.0' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.fragment:fragment-ktx:1.0.0' + implementation 'androidx.core:core-ktx:1.0.1' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + + def lifecycle_version = '2.0.0' + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + testImplementation "androidx.arch.core:core-testing:$lifecycle_version" + + implementation 'com.google.android.material:material:1.0.0' + + implementation 'com.github.bumptech.glide:glide:4.9.0' + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + testImplementation 'org.robolectric:robolectric:4.2' + testImplementation "androidx.arch.core:core-testing:$lifecycle_version" + testImplementation 'androidx.test.ext:junit:1.1.0' + testImplementation 'androidx.test.espresso:espresso-core:3.1.1' + testImplementation 'androidx.test.ext:truth:1.1.0' + testImplementation 'com.google.truth:truth:0.42' +} diff --git a/notification/Bubbles/app/proguard-rules.pro b/notification/Bubbles/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/notification/Bubbles/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt new file mode 100644 index 00000000..81a8fde2 --- /dev/null +++ b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/BubbleActivityTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import android.app.Application +import android.content.Intent +import android.net.Uri +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withHint +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BubbleActivityTest { + + @Test + fun showsChatFragment() { + ActivityScenario.launch<BubbleActivity>( + Intent(ApplicationProvider.getApplicationContext<Application>(), BubbleActivity::class.java) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.parse("https://android.example.com/chat/1")) + ).use { + onView(withHint("Type a message…")).check(matches(isDisplayed())) + } + } +} diff --git a/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt new file mode 100644 index 00000000..c817477a --- /dev/null +++ b/notification/Bubbles/app/src/androidTest/java/com/example/android/bubbles/MainActivityTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withHint +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MainActivityTest { + + @Test + fun navigateToChatFragment() { + ActivityScenario.launch(MainActivity::class.java).use { + onView(withText("Cat")) + .check(matches(isDisplayed())) + .perform(click()) + onView(withHint("Type a message…")).check(matches(isDisplayed())) + } + } +} diff --git a/notification/Bubbles/app/src/main/AndroidManifest.xml b/notification/Bubbles/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..45fac5e7 --- /dev/null +++ b/notification/Bubbles/app/src/main/AndroidManifest.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.example.android.bubbles"> + + <application + android:allowBackup="false" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.Bubbles" + tools:ignore="GoogleAppIndexingWarning"> + + <!-- + Our main entry point. + --> + <activity + android:name=".MainActivity" + android:launchMode="singleTop"> + <!-- + This activity is the one that's shown on the launcher. + --> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <!-- + This is used as the content URI of notifications. It navigates directly to the specified chat screen. + --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data + android:host="android.example.com" + android:pathPattern="/chat/*" + android:scheme="https" /> + </intent-filter> + </activity> + + <!-- + The dummy voice-call screen. + This Activity can be launched from inside an expanded Bubble. Since this Activity is launched as a new task, + it is opened as a full Activity, rather than stacked inside the expanded Bubble. + --> + <activity + android:name=".VoiceCallActivity" + android:launchMode="singleInstance" + android:theme="@style/Theme.Bubbles.Voice" /> + + <!-- + This Activity is the expanded Bubble. For that, this Activity has to have several attributes. + - allowEmbedded="true": The expanded Bubble is embedded in the System UI. + - resizeableActivity="true": The expanded Bubble is resized by the System UI. + - documentLaunchMode="always": We show multiple bubbles in this sample. There will be multiple instances of + this Activity. + --> + <activity + android:name=".BubbleActivity" + android:allowEmbedded="true" + android:documentLaunchMode="always" + android:resizeableActivity="true"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data + android:host="android.example.com" + android:pathPattern="/chat/*" + android:scheme="https" /> + </intent-filter> + </activity> + + </application> + +</manifest> diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt new file mode 100644 index 00000000..bae103a8 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/BubbleActivity.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import android.os.Bundle +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.transaction +import com.example.android.bubbles.ui.chat.ChatFragment +import com.example.android.bubbles.ui.photo.PhotoFragment + +/** + * Entry point of the app when it is launched as an expanded Bubble. + */ +class BubbleActivity : AppCompatActivity(), NavigationController { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.bubble_activity) + val id = intent.data.lastPathSegment.toLongOrNull() ?: return + if (savedInstanceState == null) { + supportFragmentManager.transaction(now = true) { + replace(R.id.container, ChatFragment.newInstance(id, false)) + } + } + } + + override fun openChat(id: Long) { + throw UnsupportedOperationException("BubbleActivity always shows a single chat thread.") + } + + override fun openPhoto(photo: Int) { + // In an expanded Bubble, you can navigate between Fragments just like you would normally do in a normal + // Activity. Just make sure you don't block onBackPressed(). + supportFragmentManager.transaction { + addToBackStack(null) + replace(R.id.container, PhotoFragment.newInstance(photo)) + } + } + + override fun updateAppBar(showContact: Boolean, hidden: Boolean, body: (name: TextView, icon: ImageView) -> Unit) { + // The expanded bubble does not have an app bar. Ignore. + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt new file mode 100644 index 00000000..c01f4f7b --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/MainActivity.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import android.content.Intent +import android.os.Bundle +import android.transition.Transition +import android.transition.TransitionInflater +import android.transition.TransitionManager +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.transaction +import com.example.android.bubbles.ui.chat.ChatFragment +import com.example.android.bubbles.ui.main.MainFragment +import com.example.android.bubbles.ui.photo.PhotoFragment + +/** + * Entry point of the app when it is launched as a full app. + */ +class MainActivity : AppCompatActivity(), NavigationController { + + companion object { + private const val FRAGMENT_CHAT = "chat" + } + + private lateinit var appBar: ViewGroup + private lateinit var name: TextView + private lateinit var icon: ImageView + + private lateinit var transition: Transition + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main_activity) + setSupportActionBar(findViewById(R.id.toolbar)) + transition = TransitionInflater.from(this).inflateTransition(R.transition.app_bar) + appBar = findViewById(R.id.app_bar) + name = findViewById(R.id.name) + icon = findViewById(R.id.icon) + if (savedInstanceState == null) { + supportFragmentManager.transaction(now = true) { + replace(R.id.container, MainFragment()) + } + intent?.let(::handleIntent) + } + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + if (intent != null) { + handleIntent(intent) + } + } + + private fun handleIntent(intent: Intent) { + if (intent.action == Intent.ACTION_VIEW) { + val id = intent.data.lastPathSegment.toLongOrNull() + if (id != null) { + openChat(id) + } + } + } + + override fun updateAppBar(showContact: Boolean, hidden: Boolean, body: (name: TextView, icon: ImageView) -> Unit) { + if (hidden) { + appBar.visibility = View.GONE + } else { + appBar.visibility = View.VISIBLE + TransitionManager.beginDelayedTransition(appBar, transition) + if (showContact) { + supportActionBar?.setDisplayShowTitleEnabled(false) + name.visibility = View.VISIBLE + icon.visibility = View.VISIBLE + } else { + supportActionBar?.setDisplayShowTitleEnabled(true) + name.visibility = View.GONE + icon.visibility = View.GONE + } + } + body(name, icon) + } + + override fun openChat(id: Long) { + supportFragmentManager.popBackStack(FRAGMENT_CHAT, FragmentManager.POP_BACK_STACK_INCLUSIVE) + supportFragmentManager.transaction { + addToBackStack(FRAGMENT_CHAT) + replace(R.id.container, ChatFragment.newInstance(id, true)) + } + } + + override fun openPhoto(photo: Int) { + supportFragmentManager.transaction { + addToBackStack(null) + replace(R.id.container, PhotoFragment.newInstance(photo)) + } + } +} + diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt new file mode 100644 index 00000000..c15c4ddb --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/NavigationController.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.fragment.app.Fragment + +/** + * Common interface between [MainActivity] and [BubbleActivity]. + */ +interface NavigationController { + + fun openChat(id: Long) + + fun openPhoto(@DrawableRes photo: Int) + + /** + * Updates the appearance and functionality of the app bar. + * + * @param showContact Whether to show contact information instead the screen title. + * @param hidden Whether to hide the app bar. + * @param body Provide this function to update the content of the app bar. + */ + fun updateAppBar( + showContact: Boolean = true, + hidden: Boolean = false, + body: (name: TextView, icon: ImageView) -> Unit = { _, _ -> } + ) +} + +fun Fragment.getNavigationController() = requireActivity() as NavigationController diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt new file mode 100644 index 00000000..46917695 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/VoiceCallActivity.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import android.os.Bundle +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions + +/** + * A dummy voice call screen. It only shows the icon and the name. + */ +class VoiceCallActivity : AppCompatActivity() { + + companion object { + const val EXTRA_NAME = "name" + const val EXTRA_ICON = "icon" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.voice_call_activity) + val name = intent.getStringExtra(EXTRA_NAME) + val icon = intent.getIntExtra(EXTRA_ICON, 0) + if (name == null || icon == 0) { + finish() + return + } + val textName: TextView = findViewById(R.id.name) + textName.text = name + val imageIcon: ImageView = findViewById(R.id.icon) + Glide.with(imageIcon).load(icon).apply(RequestOptions.circleCropTransform()).into(imageIcon) + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt new file mode 100644 index 00000000..0d0fc5f4 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Chat.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +typealias ChatThreadListener = (List<Message>) -> Unit + +class Chat(val contact: Contact) { + + private val listeners = mutableListOf<ChatThreadListener>() + + private val _messages = mutableListOf( + Message(1L, contact.id, "Send me a message", null, System.currentTimeMillis()), + Message(2L, contact.id, "I will reply in 5 seconds", null, System.currentTimeMillis()) + ) + val messages: List<Message> + get() = _messages + + fun addListener(listener: ChatThreadListener) { + listeners.add(listener) + } + + fun removeListener(listener: ChatThreadListener) { + listeners.remove(listener) + } + + fun addMessage(builder: Message.Builder) { + builder.id = _messages.last().id + 1 + _messages.add(builder.build()) + listeners.forEach { listener -> listener(_messages) } + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt new file mode 100644 index 00000000..cf3af753 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/ChatRepository.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +import android.content.Context +import androidx.annotation.MainThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +interface ChatRepository { + fun getContacts(): LiveData<List<Contact>> + fun findContact(id: Long): LiveData<Contact?> + fun findMessages(id: Long): LiveData<List<Message>> + fun sendMessage(id: Long, text: String) + fun activateChat(id: Long) + fun deactivateChat(id: Long) + fun showAsBubble(id: Long) + fun canBubble(): Boolean +} + +class DefaultChatRepository internal constructor( + private val notificationHelper: NotificationHelper, + private val executor: Executor +) : ChatRepository { + + companion object { + private var instance: DefaultChatRepository? = null + + fun getInstance(context: Context): DefaultChatRepository { + return instance ?: synchronized(this) { + instance ?: DefaultChatRepository( + NotificationHelper(context), + Executors.newFixedThreadPool(4) + ).also { + instance = it + } + } + } + } + + private var currentChat: Long = 0L + + private val chats = Contact.CONTACTS.map { contact -> + contact.id to Chat(contact) + }.toMap() + + init { + notificationHelper.setUpNotificationChannels() + } + + @MainThread + override fun getContacts(): LiveData<List<Contact>> { + return MutableLiveData<List<Contact>>().apply { + postValue(Contact.CONTACTS) + } + } + + @MainThread + override fun findContact(id: Long): LiveData<Contact?> { + return MutableLiveData<Contact>().apply { + postValue(Contact.CONTACTS.find { it.id == id }) + } + } + + @MainThread + override fun findMessages(id: Long): LiveData<List<Message>> { + val chat = chats.getValue(id) + return object : LiveData<List<Message>>() { + + private val listener = { messages: List<Message> -> + postValue(messages) + } + + override fun onActive() { + value = chat.messages + chat.addListener(listener) + } + + override fun onInactive() { + chat.removeListener(listener) + } + } + } + + @MainThread + override fun sendMessage(id: Long, text: String) { + val chat = chats.getValue(id) + chat.addMessage(Message.Builder().apply { + sender = 0L // User + this.text = text + timestamp = System.currentTimeMillis() + }) + executor.execute { + // The animal is typing... + Thread.sleep(5000L) + // Receive a reply. + chat.addMessage(chat.contact.reply(text)) + // Show notification if the chat is not on the foreground. + if (chat.contact.id != currentChat) { + notificationHelper.showNotification(chat, false) + } + } + } + + override fun activateChat(id: Long) { + currentChat = id + notificationHelper.dismissNotification(id) + } + + override fun deactivateChat(id: Long) { + if (currentChat == id) { + currentChat = 0 + } + } + + override fun showAsBubble(id: Long) { + val chat = chats.getValue(id) + executor.execute { + notificationHelper.showNotification(chat, true) + } + } + + override fun canBubble(): Boolean { + return notificationHelper.canBubble() + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt new file mode 100644 index 00000000..28798f32 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Contact.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +import androidx.annotation.DrawableRes +import com.example.android.bubbles.R + +abstract class Contact( + val id: Long, + val name: String, + @DrawableRes + val icon: Int +) { + + companion object { + val CONTACTS = listOf( + object : Contact(1L, "Cat", R.drawable.cat) { + override fun reply(text: String) = buildReply().apply { this.text = "Meow" } + }, + object : Contact(2L, "Dog", R.drawable.dog) { + override fun reply(text: String) = buildReply().apply { this.text = "Woof woof!!" } + }, + object : Contact(3L, "Parrot", R.drawable.parrot) { + override fun reply(text: String) = buildReply().apply { this.text = text } + }, + object : Contact(4L, "Sheep", R.drawable.sheep) { + override fun reply(text: String) = buildReply().apply { + this.text = "Look at me!" + photo = R.drawable.sheep_full + } + } + ) + } + + fun buildReply() = Message.Builder().apply { + sender = this@Contact.id + timestamp = System.currentTimeMillis() + } + + abstract fun reply(text: String): Message.Builder + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Contact + + if (id != other.id) return false + if (name != other.name) return false + if (icon != other.icon) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + icon + return result + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt new file mode 100644 index 00000000..bc48a0f6 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/Message.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +import androidx.annotation.DrawableRes + +data class Message( + val id: Long, + val sender: Long, + val text: String, + @DrawableRes + val photo: Int?, + val timestamp: Long +) { + + val isIncoming: Boolean + get() = sender != 0L + + class Builder { + var id: Long? = null + var sender: Long? = null + var text: String? = null + var photo: Int? = null + var timestamp: Long? = null + fun build() = Message(id!!, sender!!, text!!, photo, timestamp!!) + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt new file mode 100644 index 00000000..825489c3 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/data/NotificationHelper.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Person +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.BlendMode +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.drawable.Icon +import android.net.Uri +import androidx.annotation.DrawableRes +import androidx.annotation.WorkerThread +import androidx.core.graphics.applyCanvas +import com.example.android.bubbles.BubbleActivity +import com.example.android.bubbles.MainActivity +import com.example.android.bubbles.R + +/** + * Handles all operations related to [Notification]. + */ +class NotificationHelper(private val context: Context) { + + companion object { + /** + * The notification channel for messages. This is used for showing Bubbles. + */ + private const val CHANNEL_NEW_MESSAGES = "new_messages" + + private const val REQUEST_CONTENT = 1 + private const val REQUEST_BUBBLE = 2 + } + + private val notificationManager = context.getSystemService(NotificationManager::class.java) + + fun setUpNotificationChannels() { + if (notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES) == null) { + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_NEW_MESSAGES, + context.getString(R.string.channel_new_messages), + // The importance must be IMPORTANCE_HIGH to show Bubbles. + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = context.getString(R.string.channel_new_messages_description) + } + ) + } + } + + @WorkerThread + fun showNotification(chat: Chat, fromUser: Boolean) { + val icon = Icon.createWithBitmap(roundIcon(context, chat.contact.icon)) + val person = Person.Builder() + .setName(chat.contact.name) + .setIcon(icon) + .build() + val contentUri = Uri.parse("https://android.example.com/chat/${chat.contact.id}") + val builder = Notification.Builder(context, CHANNEL_NEW_MESSAGES) + // A notification can be shown as a bubble by calling setBubbleMetadata() + .setBubbleMetadata( + Notification.BubbleMetadata.Builder() + // The height of the expanded bubble. + .setDesiredHeight(context.resources.getDimensionPixelSize(R.dimen.bubble_height)) + // The icon of the bubble. + // TODO: The icon is not displayed in Android Q Beta 2. + .setIcon(icon) + .apply { + // When the bubble is explicitly opened by the user, we can show the bubble automatically + // in the expanded state. This works only when the app is in the foreground. + // TODO: This does not yet work in Android Q Beta 2. + if (fromUser) { + setAutoExpandBubble(true) + setSuppressInitialNotification(true) + } + } + // The Intent to be used for the expanded bubble. + .setIntent( + PendingIntent.getActivity( + context, + REQUEST_BUBBLE, + // Launch BubbleActivity as the expanded bubble. + Intent(context, BubbleActivity::class.java) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.parse("https://android.example.com/chat/${chat.contact.id}")), + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + .build() + ) + // The user can turn off the bubble in system settings. In that case, this notification is shown as a + // normal notification instead of a bubble. Make sure that this notification works as a normal notification + // as well. + .setContentTitle(chat.contact.name) + .setSmallIcon(R.drawable.ic_message) + .setCategory(Notification.CATEGORY_MESSAGE) + .addPerson(person) + .setShowWhen(true) + // The content Intent is used when the user clicks on the "Open Content" icon button on the expanded bubble, + // as well as when the fall-back notification is clicked. + .setContentIntent( + PendingIntent.getActivity( + context, + REQUEST_CONTENT, + Intent(context, MainActivity::class.java) + .setAction(Intent.ACTION_VIEW) + .setData(contentUri), + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + + if (fromUser) { + // This is a Bubble explicitly opened by the user. + builder.setContentText(context.getString(R.string.chat_with_contact, chat.contact.name)) + } else { + // Let's add some more content to the notification in case it falls back to a normal notification. + val lastOutgoingId = chat.messages.last { !it.isIncoming }.id + val newMessages = chat.messages.filter { message -> + message.id > lastOutgoingId + } + val lastMessage = newMessages.last() + builder + .setStyle( + if (lastMessage.photo != null) { + Notification.BigPictureStyle() + .bigPicture(BitmapFactory.decodeResource(context.resources, lastMessage.photo)) + .bigLargeIcon(icon) + .setSummaryText(lastMessage.text) + } else { + Notification.MessagingStyle(person) + .apply { + for (message in newMessages) { + addMessage(message.text, message.timestamp, person) + } + } + .setGroupConversation(false) + } + ) + .setContentText(newMessages.joinToString("\n") { it.text }) + .setWhen(newMessages.last().timestamp) + } + + notificationManager.notify(chat.contact.id.toInt(), builder.build()) + } + + fun dismissNotification(id: Long) { + notificationManager.cancel(id.toInt()) + } + + fun canBubble(): Boolean { + val channel = notificationManager.getNotificationChannel(CHANNEL_NEW_MESSAGES) + return notificationManager.areBubblesAllowed() && channel.canBubble() + } +} + +@WorkerThread +private fun roundIcon(context: Context, @DrawableRes id: Int): Bitmap { + val original = BitmapFactory.decodeResource(context.resources, id) + val width = original.width + val height = original.height + val rect = Rect(0, 0, width, height) + val icon = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val paint = Paint().apply { + isAntiAlias = true + color = Color.BLACK + } + icon.applyCanvas { + drawARGB(0, 0, 0, 0) + drawOval(0f, 0f, width.toFloat(), height.toFloat(), paint) + paint.blendMode = BlendMode.SRC_IN + drawBitmap(original, rect, rect, paint) + } + return icon +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt new file mode 100644 index 00000000..4bc25484 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatFragment.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.chat + +import android.content.Intent +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.transition.TransitionInflater +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import android.widget.ImageButton +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import com.example.android.bubbles.R +import com.example.android.bubbles.VoiceCallActivity +import com.example.android.bubbles.getNavigationController + +/** + * The chat screen. This is used in the full app (MainActivity) as well as in the expanded Bubble (BubbleActivity). + */ +class ChatFragment : Fragment() { + + companion object { + private const val ARG_ID = "id" + private const val ARG_FOREGROUND = "foreground" + + fun newInstance(id: Long, foreground: Boolean) = ChatFragment().apply { + arguments = Bundle().apply { + putLong(ARG_ID, id) + putBoolean(ARG_FOREGROUND, foreground) + } + } + } + + private lateinit var viewModel: ChatViewModel + private lateinit var input: EditText + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + enterTransition = TransitionInflater.from(context).inflateTransition(R.transition.slide_bottom) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.chat_fragment, container, false) + } + + private val startPostponedTransitionOnEnd = object : RequestListener<Drawable> { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target<Drawable>?, + isFirstResource: Boolean + ): Boolean { + startPostponedEnterTransition() + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target<Drawable>?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + startPostponedEnterTransition() + return false + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val id = arguments?.getLong(ARG_ID) + if (id == null) { + fragmentManager?.popBackStack() + return + } + val navigationController = getNavigationController() + + viewModel = ViewModelProviders.of(this).get(ChatViewModel::class.java) + viewModel.setChatId(id) + + val messages: RecyclerView = view.findViewById(R.id.messages) + val voiceCall: ImageButton = view.findViewById(R.id.voice_call) + input = view.findViewById(R.id.input) + val send: ImageButton = view.findViewById(R.id.send) + + val messageAdapter = MessageAdapter(view.context) { photo -> + navigationController.openPhoto(photo) + } + val linearLayoutManager = LinearLayoutManager(view.context).apply { + stackFromEnd = true + } + messages.run { + layoutManager = linearLayoutManager + adapter = messageAdapter + } + + viewModel.contact.observe(viewLifecycleOwner, Observer { chat -> + if (chat == null) { + Toast.makeText(view.context, "Contact not found", Toast.LENGTH_SHORT).show() + fragmentManager?.popBackStack() + } else { + navigationController.updateAppBar { name, icon -> + name.text = chat.name + Glide.with(icon) + .load(chat.icon) + .apply(RequestOptions.circleCropTransform()) + .dontAnimate() + .addListener(startPostponedTransitionOnEnd) + .into(icon) + } + } + }) + + viewModel.messages.observe(viewLifecycleOwner, Observer { + messageAdapter.submitList(it) + linearLayoutManager.scrollToPosition(it.size - 1) + }) + + voiceCall.setOnClickListener { + voiceCall() + } + send.setOnClickListener { + send() + } + input.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_SEND) { + send() + true + } else { + false + } + } + } + + override fun onStart() { + super.onStart() + val foreground = arguments?.getBoolean(ARG_FOREGROUND) == true + viewModel.foreground = foreground + } + + override fun onStop() { + super.onStop() + viewModel.foreground = false + } + + private fun voiceCall() { + val contact = viewModel.contact.value ?: return + startActivity( + Intent(requireActivity(), VoiceCallActivity::class.java) + .putExtra(VoiceCallActivity.EXTRA_NAME, contact.name) + .putExtra(VoiceCallActivity.EXTRA_ICON, contact.icon) + ) + } + + private fun send() { + val text = input.text.toString() + if (text.isNotEmpty()) { + input.text.clear() + viewModel.send(text) + } + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + inflater?.inflate(R.menu.chat, menu) + menu?.findItem(R.id.action_show_as_bubble)?.let { item -> + viewModel.showAsBubbleVisible.observe(viewLifecycleOwner, Observer { + item.isVisible = it + }) + } + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return when (item?.itemId) { + R.id.action_show_as_bubble -> { + viewModel.showAsBubble() + fragmentManager?.popBackStack() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt new file mode 100644 index 00000000..df8746ca --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/ChatViewModel.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.chat + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import com.example.android.bubbles.data.ChatRepository +import com.example.android.bubbles.data.Contact +import com.example.android.bubbles.data.DefaultChatRepository +import com.example.android.bubbles.data.Message + +class ChatViewModel @JvmOverloads constructor( + application: Application, + private val repository: ChatRepository = DefaultChatRepository.getInstance(application) +) : AndroidViewModel(application) { + + private val chatId = MutableLiveData<Long>() + + /** + * We want to dismiss a notification when the corresponding chat screen is open. Setting this to `true` dismisses + * the current notification and suppresses further notifications. + * + * We do want to keep on showing and updating the notification when the chat screen is opened as an expanded bubble. + * [ChatFragment] should set this to false if it is launched in BubbleActivity. Otherwise, the expanding a bubble + * would remove the notification and the bubble. + */ + var foreground = false + set(value) { + field = value + chatId.value?.let { id -> + if (value) { + repository.activateChat(id) + } else { + repository.deactivateChat(id) + } + } + } + + /** + * The contact of this chat. + */ + val contact: LiveData<Contact?> = Transformations.switchMap(chatId) { id -> + repository.findContact(id) + } + + /** + * The list of all the messages in this chat. + */ + val messages: LiveData<List<Message>> = Transformations.switchMap(chatId) { id -> + repository.findMessages(id) + } + + /** + * Whether the "Show as Bubble" button should be shown. + */ + val showAsBubbleVisible: LiveData<Boolean> = object: LiveData<Boolean>() { + override fun onActive() { + // We hide the "Show as Bubble" button if we are not allowed to show the bubble. + value = repository.canBubble() + } + } + + fun setChatId(id: Long) { + chatId.value = id + if (foreground) { + repository.activateChat(id) + } else { + repository.deactivateChat(id) + } + } + + fun send(text: String) { + val id = chatId.value + if (id != null && id != 0L) { + repository.sendMessage(id, text) + } + } + + fun showAsBubble() { + chatId.value?.let { id -> + repository.showAsBubble(id) + } + } + + override fun onCleared() { + chatId.value?.let { id -> repository.deactivateChat(id) } + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt new file mode 100644 index 00000000..9fd72428 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/chat/MessageAdapter.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.chat + +import android.content.Context +import android.content.res.ColorStateList +import android.view.Gravity +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.android.bubbles.R +import com.example.android.bubbles.data.Message + +class MessageAdapter( + context: Context, + private val onPhotoClicked: (photo: Int) -> Unit +) : ListAdapter<Message, MessageViewHolder>(DIFF_CALLBACK) { + + private val tint = object { + val incoming: ColorStateList = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.incoming)) + val outgoing: ColorStateList = ColorStateList.valueOf( + ContextCompat.getColor(context, R.color.outgoing) + ) + } + + private val padding = object { + val vertical: Int = context.resources.getDimensionPixelSize(R.dimen.message_padding_vertical) + + val horizontalShort: Int = context.resources.getDimensionPixelSize( + R.dimen.message_padding_horizontal_short + ) + + val horizontalLong: Int = context.resources.getDimensionPixelSize( + R.dimen.message_padding_horizontal_long + ) + } + + + init { + setHasStableIds(true) + } + + override fun getItemId(position: Int): Long { + return getItem(position).id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { + val holder = MessageViewHolder(parent) + holder.message.setOnClickListener { + val photo: Int? = it.getTag(R.id.tag_photo) as Int? + if (photo != null) { + onPhotoClicked(photo) + } + } + return holder + } + + override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { + val message = getItem(position) + val lp = holder.message.layoutParams as FrameLayout.LayoutParams + if (message.isIncoming) { + holder.message.run { + setBackgroundResource(R.drawable.message_incoming) + ViewCompat.setBackgroundTintList(this, tint.incoming) + setPadding( + padding.horizontalLong, padding.vertical, + padding.horizontalShort, padding.vertical + ) + layoutParams = lp.apply { + gravity = Gravity.START + } + if (message.photo != null) { + holder.message.setTag(R.id.tag_photo, message.photo) + setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, message.photo) + } else { + holder.message.setTag(R.id.tag_photo, null) + setCompoundDrawables(null, null, null, null) + } + } + } else { + holder.message.run { + setBackgroundResource(R.drawable.message_outgoing) + ViewCompat.setBackgroundTintList(this, tint.outgoing) + setPadding( + padding.horizontalShort, padding.vertical, + padding.horizontalLong, padding.vertical + ) + layoutParams = lp.apply { + gravity = Gravity.END + } + } + } + holder.message.text = message.text + } +} + +private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Message>() { + + override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean { + return oldItem == newItem + } + +} + +class MessageViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false) +) { + val message: TextView = itemView.findViewById(R.id.message) +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt new file mode 100644 index 00000000..4badf9b5 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/ContactAdapter.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.main + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.example.android.bubbles.R +import com.example.android.bubbles.data.Contact + +class ContactAdapter( + private val onChatClicked: (id: Long) -> Unit +) : ListAdapter<Contact, ContactViewHolder>(DIFF_CALLBACK) { + + init { + setHasStableIds(true) + } + + override fun getItemId(position: Int): Long { + return getItem(position).id + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder { + val holder = ContactViewHolder(parent) + holder.itemView.setOnClickListener { + onChatClicked(holder.itemId) + } + return holder + } + + override fun onBindViewHolder(holder: ContactViewHolder, position: Int) { + val contact: Contact = getItem(position) + Glide.with(holder.icon).load(contact.icon).apply(RequestOptions.circleCropTransform()).into(holder.icon) + holder.name.text = contact.name + } +} + +private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Contact>() { + override fun areItemsTheSame(oldItem: Contact, newItem: Contact): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Contact, newItem: Contact): Boolean { + return oldItem == newItem + } +} + +class ContactViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.chat_item, parent, false) +) { + val icon: ImageView = itemView.findViewById(R.id.icon) + val name: TextView = itemView.findViewById(R.id.name) +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt new file mode 100644 index 00000000..ee4b9207 --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainFragment.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.main + +import android.os.Bundle +import android.transition.TransitionInflater +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.android.bubbles.R +import com.example.android.bubbles.getNavigationController + +/** + * The main chat list screen. + */ +class MainFragment : Fragment() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + exitTransition = TransitionInflater.from(context).inflateTransition(R.transition.slide_top) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.main_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val navigationController = getNavigationController() + navigationController.updateAppBar(false) + val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) + + val contactAdapter = ContactAdapter { id -> + navigationController.openChat(id) + } + viewModel.contacts.observe(viewLifecycleOwner, Observer { contacts -> + contactAdapter.submitList(contacts) + }) + + view.findViewById<RecyclerView>(R.id.contacts).run { + layoutManager = LinearLayoutManager(view.context) + setHasFixedSize(true) + adapter = contactAdapter + } + } +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt new file mode 100644 index 00000000..f959352c --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/main/MainViewModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.main + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import com.example.android.bubbles.data.ChatRepository +import com.example.android.bubbles.data.DefaultChatRepository + +class MainViewModel @JvmOverloads constructor( + application: Application, + repository: ChatRepository = DefaultChatRepository.getInstance(application) +) : AndroidViewModel(application) { + + /** + * All the contacts. + */ + val contacts = repository.getContacts() +} diff --git a/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt new file mode 100644 index 00000000..14f6594d --- /dev/null +++ b/notification/Bubbles/app/src/main/java/com/example/android/bubbles/ui/photo/PhotoFragment.kt @@ -0,0 +1,47 @@ +package com.example.android.bubbles.ui.photo + +import android.os.Bundle +import android.transition.Fade +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.fragment.app.Fragment +import com.example.android.bubbles.R +import com.example.android.bubbles.getNavigationController + +/** + * Shows the specified [DrawableRes] as a full-screen photo. + */ +class PhotoFragment : Fragment() { + + companion object { + private const val ARG_PHOTO = "photo" + + fun newInstance(@DrawableRes photo: Int) = PhotoFragment().apply { + arguments = Bundle().apply { + putInt(ARG_PHOTO, photo) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = Fade() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.photo_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val photoResId = arguments?.getInt(ARG_PHOTO) + if (photoResId == null) { + fragmentManager?.popBackStack() + return + } + getNavigationController().updateAppBar(hidden = true) + view.findViewById<ImageView>(R.id.photo).setImageResource(photoResId) + } +} diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg Binary files differnew file mode 100644 index 00000000..61961708 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/cat.jpg diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg Binary files differnew file mode 100644 index 00000000..df159007 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/dog.jpg diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg Binary files differnew file mode 100644 index 00000000..8e2e18df --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/parrot.jpg diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg Binary files differnew file mode 100644 index 00000000..39876f9c --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep.jpg diff --git a/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg Binary files differnew file mode 100644 index 00000000..ad5e8c15 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable-nodpi/sheep_full.jpg diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml b/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..f7cf56d0 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,18 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="55.644802" + android:viewportHeight="55.644802"> + <group android:translateX="3.4717517" + android:translateY="3.4717517"> + <group android:translateX="-1.2175325" + android:translateY="-1.2175325"> + <group android:translateX="13.568182" + android:translateY="13.568182"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> + </group> + </group> + </group> +</vector> diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_message.xml b/notification/Bubbles/app/src/main/res/drawable/ic_message.xml new file mode 100644 index 00000000..d2876bfa --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/ic_message.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> +</vector> diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml b/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml new file mode 100644 index 00000000..bd528267 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/ic_open_in_new.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="#FFFFFF" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z" /> +</vector> diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_send.xml b/notification/Bubbles/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 00000000..e145ca83 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/> +</vector> diff --git a/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml b/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml new file mode 100644 index 00000000..ebf9de60 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/ic_voice_call.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/> +</vector> diff --git a/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml b/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml new file mode 100644 index 00000000..94871b4b --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/message_incoming.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <rotate + android:fromDegrees="-45" + android:pivotX="0%" + android:pivotY="0%" + android:toDegrees="0"> + <shape android:shape="rectangle"> + <solid android:color="#fff" /> + </shape> + </rotate> + </item> + <item android:left="8dp"> + <shape android:shape="rectangle"> + <solid android:color="#fff" /> + <corners android:radius="4dp" /> + </shape> + </item> +</layer-list> diff --git a/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml b/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml new file mode 100644 index 00000000..39cc7b1b --- /dev/null +++ b/notification/Bubbles/app/src/main/res/drawable/message_outgoing.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2017 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. +--> +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <rotate + android:fromDegrees="45" + android:pivotX="100%" + android:pivotY="0%" + android:toDegrees="0"> + <shape android:shape="rectangle"> + <solid android:color="#fff" /> + </shape> + </rotate> + </item> + <item android:right="8dp"> + <shape android:shape="rectangle"> + <solid android:color="#fff" /> + <corners android:radius="4dp" /> + </shape> + </item> +</layer-list> diff --git a/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml b/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml new file mode 100644 index 00000000..6a766a07 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/bubble_activity.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml b/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml new file mode 100644 index 00000000..4ce86e4b --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/chat_fragment.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/chat" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/messages" + style="?attr/buttonBarStyle" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:clipToPadding="false" + android:paddingTop="@dimen/spacing_small" + android:paddingBottom="@dimen/spacing_small" + android:scrollbars="vertical" /> + + <LinearLayout + android:id="@+id/input_bar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?android:attr/windowBackground" + android:elevation="@dimen/app_bar_elevation" + android:orientation="horizontal"> + + <ImageButton + android:id="@+id/voice_call" + style="?attr/buttonBarNeutralButtonStyle" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:contentDescription="@string/description_voice_call" + android:tint="?attr/colorAccent" + app:srcCompat="@drawable/ic_voice_call" /> + + <EditText + android:id="@+id/input" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:hint="@string/hint_input" + android:imeOptions="actionSend" + android:importantForAutofill="no" + android:inputType="textCapSentences" /> + + <ImageButton + android:id="@+id/send" + style="?attr/buttonBarNeutralButtonStyle" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:contentDescription="@string/description_send" + android:tint="?attr/colorAccent" + app:srcCompat="@drawable/ic_send" /> + + </LinearLayout> + +</LinearLayout> diff --git a/notification/Bubbles/app/src/main/res/layout/chat_item.xml b/notification/Bubbles/app/src/main/res/layout/chat_item.xml new file mode 100644 index 00000000..6f2bafcf --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/chat_item.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/chat" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:minHeight="?attr/listPreferredItemHeightLarge" + android:orientation="horizontal" + android:paddingStart="@dimen/spacing_small" + android:paddingEnd="@dimen/spacing_small" + tools:ignore="UseCompoundDrawables"> + + <ImageView + android:id="@+id/icon" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_gravity="center_vertical" + android:layout_margin="@dimen/spacing_small" + android:contentDescription="@string/description_icon" + tools:src="@drawable/cat" /> + + <TextView + android:id="@+id/name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_margin="@dimen/spacing_small" + android:layout_weight="1" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.AppCompat.Large" + tools:text="Cat" /> + +</LinearLayout> diff --git a/notification/Bubbles/app/src/main/res/layout/main_activity.xml b/notification/Bubbles/app/src/main/res/layout/main_activity.xml new file mode 100644 index 00000000..df2a66e1 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".MainActivity"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/app_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorPrimary" + android:elevation="@dimen/app_bar_elevation" + android:theme="@style/ThemeOverlay.AppCompat.Dark"> + + <ImageView + android:id="@+id/icon" + android:layout_width="@dimen/icon_size" + android:layout_height="@dimen/icon_size" + android:layout_marginVertical="@dimen/spacing_small" + android:layout_marginStart="@dimen/spacing_medium" + android:contentDescription="@string/description_icon" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@drawable/cat" /> + + <TextView + android:id="@+id/name" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/spacing_medium" + android:gravity="center_vertical" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.AppCompat.Large" + app:layout_constraintBottom_toBottomOf="@id/icon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/icon" + app:layout_constraintTop_toTopOf="@id/icon" + tools:text="Cat" /> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="0dp" + android:layout_height="match_parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <FrameLayout + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + +</LinearLayout> diff --git a/notification/Bubbles/app/src/main/res/layout/main_fragment.xml b/notification/Bubbles/app/src/main/res/layout/main_fragment.xml new file mode 100644 index 00000000..36a89bc7 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/main_fragment.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/contacts" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingTop="@dimen/spacing_small" + android:paddingBottom="@dimen/spacing_small" /> diff --git a/notification/Bubbles/app/src/main/res/layout/message_item.xml b/notification/Bubbles/app/src/main/res/layout/message_item.xml new file mode 100644 index 00000000..b162a945 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/message_item.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_small" /> + +</FrameLayout> diff --git a/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml b/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml new file mode 100644 index 00000000..15b63d38 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/photo_fragment.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/photo" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:scaleType="fitCenter" + tools:src="@drawable/sheep_full" + android:contentDescription="@string/description_photo" /> diff --git a/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml b/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml new file mode 100644 index 00000000..9326eb7d --- /dev/null +++ b/notification/Bubbles/app/src/main/res/layout/voice_call_activity.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/voice" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".VoiceCallActivity"> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" /> + + <ImageView + android:id="@+id/icon" + android:layout_width="@dimen/icon_size" + android:layout_height="@dimen/icon_size" + android:layout_gravity="center_horizontal" + android:layout_margin="@dimen/spacing_medium" + android:contentDescription="@string/description_icon" + tools:src="@drawable/cat" /> + + <TextView + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_medium" + android:gravity="center_horizontal" + android:textAppearance="@style/TextAppearance.AppCompat.Large" + tools:text="Cat" /> + + <TextView + android:id="@+id/explanation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_medium" + android:gravity="center_horizontal" + android:text="@string/voice_call_explanation" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" /> + +</LinearLayout> diff --git a/notification/Bubbles/app/src/main/res/menu/chat.xml b/notification/Bubbles/app/src/main/res/menu/chat.xml new file mode 100644 index 00000000..f7b1269c --- /dev/null +++ b/notification/Bubbles/app/src/main/res/menu/chat.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_show_as_bubble" + android:icon="@drawable/ic_open_in_new" + android:title="@string/show_as_bubble" + app:showAsAction="ifRoom" /> + +</menu> diff --git a/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..7353dbd1 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..7353dbd1 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..add49201 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..36a7f52a --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..c1b13dd4 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..d7c39d6c --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..f7747291 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..f0e3e03e --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..85e929ff --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..156450ac --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..51b123c7 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..569927db --- /dev/null +++ b/notification/Bubbles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/notification/Bubbles/app/src/main/res/transition/app_bar.xml b/notification/Bubbles/app/src/main/res/transition/app_bar.xml new file mode 100644 index 00000000..462cfd4c --- /dev/null +++ b/notification/Bubbles/app/src/main/res/transition/app_bar.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<transitionSet + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="250" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:transitionOrdering="together"> + + <fade /> + <changeBounds /> + +</transitionSet> diff --git a/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml b/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml new file mode 100644 index 00000000..f41bfb42 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/transition/slide_bottom.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<slide + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="250" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:slideEdge="bottom" /> diff --git a/notification/Bubbles/app/src/main/res/transition/slide_top.xml b/notification/Bubbles/app/src/main/res/transition/slide_top.xml new file mode 100644 index 00000000..9b68cd70 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/transition/slide_top.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<slide + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="250" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:slideEdge="top" /> diff --git a/notification/Bubbles/app/src/main/res/values/colors.xml b/notification/Bubbles/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..b4ca87b3 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/colors.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<resources> + <color name="primary">#008577</color> + <color name="primary_dark">#00574B</color> + <color name="accent">#D81B60</color> + + <color name="incoming">#FBE9E7</color> + <color name="outgoing">#EEEEEE</color> +</resources> diff --git a/notification/Bubbles/app/src/main/res/values/dimens.xml b/notification/Bubbles/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..be251581 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/dimens.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<resources> + <dimen name="spacing_medium">16dp</dimen> + <dimen name="spacing_small">8dp</dimen> + <dimen name="icon_size">64dp</dimen> + <dimen name="app_bar_elevation">4dp</dimen> + <dimen name="message_padding_vertical">16dp</dimen> + <dimen name="message_padding_horizontal_short">16dp</dimen> + <dimen name="message_padding_horizontal_long">24dp</dimen> + <dimen name="bubble_height">400dp</dimen> +</resources> diff --git a/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml b/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..c1333504 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#1C7A71</color> +</resources>
\ No newline at end of file diff --git a/notification/Bubbles/app/src/main/res/values/ids.xml b/notification/Bubbles/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..e205a5bf --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/ids.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<resources> + <item name="tag_photo" type="id" /> +</resources> diff --git a/notification/Bubbles/app/src/main/res/values/strings.xml b/notification/Bubbles/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..aae789ed --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/strings.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<resources> + <string name="app_name">Bubbles</string> + <string name="show_as_bubble">Show as Bubble</string> + <string name="description_icon">Profile icon</string> + <string name="description_voice_call">Make a voice call (dummy)</string> + <string name="description_send">Send</string> + <string name="description_photo">Photo</string> + <string name="hint_input">Type a message…</string> + <string name="channel_new_messages">New messages</string> + <string name="channel_new_messages_description">All new incoming messages.</string> + <string name="chat_with_contact">Chat with %s</string> + <string name="voice_call_explanation">This is a dummy voice call screen.</string> +</resources> diff --git a/notification/Bubbles/app/src/main/res/values/styles.xml b/notification/Bubbles/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..b85424d7 --- /dev/null +++ b/notification/Bubbles/app/src/main/res/values/styles.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<resources> + + <style name="Theme.Bubbles" parent="Theme.AppCompat.Light.NoActionBar"> + <item name="colorPrimary">@color/primary</item> + <item name="colorPrimaryDark">@color/primary_dark</item> + <item name="colorAccent">@color/accent</item> + </style> + + <style name="Theme.Bubbles.Voice" parent="Theme.AppCompat.NoActionBar"> + <item name="colorPrimary">@color/primary</item> + <item name="colorPrimaryDark">@color/primary_dark</item> + <item name="colorAccent">@color/accent</item> + </style> + +</resources> diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt new file mode 100644 index 00000000..6c74bb36 --- /dev/null +++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/LiveDataTestUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.test.platform.app.InstrumentationRegistry +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Observes this [LiveData] and returns the value. + * + * @throws NullPointerException if the observed value is null. + */ +fun <T> LiveData<T>.observedValue(): T { + var result: T? = null + val latch = CountDownLatch(1) + val observer = Observer<T> { + result = it + latch.countDown() + } + InstrumentationRegistry.getInstrumentation().runOnMainSync { + observeForever(observer) + } + latch.await(3000L, TimeUnit.MILLISECONDS) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + removeObserver(observer) + } + return result!! +} diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt new file mode 100644 index 00000000..65d8d9c9 --- /dev/null +++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/data/TestChatRepository.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.data + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData + +/** + * This is like [DefaultChatRepository] except: + * - The initial chat history can be supplied as a constructor parameter. + * - It does not wait 5 seconds to receive a reply. + */ +class TestChatRepository(private val chats: Map<Long, Chat>) : ChatRepository { + + var activatedId = 0L + + var bubbleId = 0L + + override fun getContacts(): LiveData<List<Contact>> { + return MutableLiveData<List<Contact>>().apply { + value = chats.values.map { it.contact } + } + } + + override fun findContact(id: Long): LiveData<Contact?> { + return MutableLiveData<Contact>().apply { + value = Contact.CONTACTS.find { it.id == id } + } + } + + override fun findMessages(id: Long): LiveData<List<Message>> { + val chat = chats.getValue(id) + return object : LiveData<List<Message>>() { + + private val listener = { messages: List<Message> -> + postValue(messages) + } + + override fun onActive() { + value = chat.messages + chat.addListener(listener) + } + + override fun onInactive() { + chat.removeListener(listener) + } + } + } + + override fun sendMessage(id: Long, text: String) { + val chat = chats.getValue(id) + chat.addMessage(Message.Builder().apply { + sender = 0L // User + this.text = text + timestamp = System.currentTimeMillis() + }) + chat.addMessage(chat.contact.reply(text)) + } + + override fun activateChat(id: Long) { + activatedId = id + } + + override fun deactivateChat(id: Long) { + activatedId = 0L + } + + override fun showAsBubble(id: Long) { + bubbleId = id + } + + override fun canBubble(): Boolean { + return true + } +} diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt new file mode 100644 index 00000000..6a136399 --- /dev/null +++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/chat/ChatViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.chat + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.example.android.bubbles.data.Chat +import com.example.android.bubbles.data.Contact +import com.example.android.bubbles.data.TestChatRepository +import com.example.android.bubbles.observedValue +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ChatViewModelTest { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private val dummyContacts = Contact.CONTACTS + + private lateinit var viewModel: ChatViewModel + private lateinit var repository: TestChatRepository + + @Before + fun createViewModel() { + repository = TestChatRepository(dummyContacts.map { contact -> + contact.id to Chat(contact) + }.toMap()) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + viewModel = ChatViewModel(ApplicationProvider.getApplicationContext(), repository) + } + } + + @Test + fun hasContactAndMessages() { + viewModel.setChatId(1L) + viewModel.foreground = true + assertThat(viewModel.contact.observedValue()).isEqualTo(dummyContacts.find { it.id == 1L }) + assertThat(viewModel.messages.observedValue()).hasSize(2) + assertThat(repository.activatedId).isEqualTo(1L) + } + + @Test + fun sendAndReceiveReply() { + viewModel.setChatId(1L) + viewModel.send("a") + val messages = viewModel.messages.observedValue() + assertThat(messages).hasSize(4) + assertThat(messages[2].text).isEqualTo("a") + assertThat(messages[3].text).isEqualTo("Meow") + } + + @Test + fun showAsBubble() { + viewModel.setChatId(1L) + assertThat(repository.bubbleId).isEqualTo(0L) + viewModel.showAsBubble() + assertThat(repository.bubbleId).isEqualTo(1L) + } + +} diff --git a/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt new file mode 100644 index 00000000..4f1f5e6a --- /dev/null +++ b/notification/Bubbles/app/src/test/java/com/example/android/bubbles/ui/main/MainViewModelTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 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 com.example.android.bubbles.ui.main + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.example.android.bubbles.data.Chat +import com.example.android.bubbles.data.Contact +import com.example.android.bubbles.data.TestChatRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MainViewModelTest { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private val dummyContacts = Contact.CONTACTS + + private fun createViewModel(): MainViewModel { + var viewModel: MainViewModel? = null + InstrumentationRegistry.getInstrumentation().runOnMainSync { + viewModel = MainViewModel( + ApplicationProvider.getApplicationContext(), + TestChatRepository(dummyContacts.map { contact -> + contact.id to Chat(contact) + }.toMap()) + ) + } + return viewModel!! + } + + @Test + fun hasListOfContacts() { + val viewModel = createViewModel() + val contacts = viewModel.contacts.value + assertThat(contacts).isEqualTo(dummyContacts) + } + +} diff --git a/notification/Bubbles/build.gradle b/notification/Bubbles/build.gradle new file mode 100644 index 00000000..d1db5ac2 --- /dev/null +++ b/notification/Bubbles/build.gradle @@ -0,0 +1,36 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.21' + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + + +// BEGIN_EXCLUDE +import com.example.android.samples.build.SampleGenPlugin + +apply plugin: SampleGenPlugin +samplegen { + pathToBuild "../../../../build" + pathToSamplesCommon "../../common" +} +apply from: "../../../../build/build.gradle" +// END_EXCLUDE diff --git a/notification/Bubbles/buildSrc/build.gradle b/notification/Bubbles/buildSrc/build.gradle new file mode 100644 index 00000000..8963e1f8 --- /dev/null +++ b/notification/Bubbles/buildSrc/build.gradle @@ -0,0 +1,18 @@ + +repositories { + google() + jcenter() + mavenCentral() +} +dependencies { + compile 'org.freemarker:freemarker:2.3.20' +} + +sourceSets { + main { + groovy { + srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy") + } + } +} + diff --git a/notification/Bubbles/gradle.properties b/notification/Bubbles/gradle.properties new file mode 100644 index 00000000..23339e0d --- /dev/null +++ b/notification/Bubbles/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar b/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 00000000..87b738cb --- /dev/null +++ b/notification/Bubbles/gradle/wrapper/gradle-wrapper.jar diff --git a/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties b/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ae45383b --- /dev/null +++ b/notification/Bubbles/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/notification/Bubbles/gradlew b/notification/Bubbles/gradlew new file mode 100755 index 00000000..af6708ff --- /dev/null +++ b/notification/Bubbles/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/notification/Bubbles/gradlew.bat b/notification/Bubbles/gradlew.bat new file mode 100644 index 00000000..0f8d5937 --- /dev/null +++ b/notification/Bubbles/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/notification/Bubbles/screenshots/bubble.png b/notification/Bubbles/screenshots/bubble.png Binary files differnew file mode 100644 index 00000000..bfa55cfb --- /dev/null +++ b/notification/Bubbles/screenshots/bubble.png diff --git a/notification/Bubbles/screenshots/chat.png b/notification/Bubbles/screenshots/chat.png Binary files differnew file mode 100644 index 00000000..55c475bb --- /dev/null +++ b/notification/Bubbles/screenshots/chat.png diff --git a/notification/Bubbles/screenshots/icon-web.png b/notification/Bubbles/screenshots/icon-web.png Binary files differnew file mode 100644 index 00000000..2c18de9e --- /dev/null +++ b/notification/Bubbles/screenshots/icon-web.png diff --git a/notification/Bubbles/screenshots/main.png b/notification/Bubbles/screenshots/main.png Binary files differnew file mode 100644 index 00000000..ab89c979 --- /dev/null +++ b/notification/Bubbles/screenshots/main.png diff --git a/notification/Bubbles/settings.gradle b/notification/Bubbles/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/notification/Bubbles/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/notification/Bubbles/template-params.xml b/notification/Bubbles/template-params.xml new file mode 100644 index 00000000..7c59c345 --- /dev/null +++ b/notification/Bubbles/template-params.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> +<sample> + <name>Bubbles</name> + <group>Notification</group> + <package>com.example.android.bubbles</package> + + <minSdk>Q</minSdk> + + <strings> + <intro> + <![CDATA[ +This sample demonstrates how to use Bubbles API to show notifications as bubbles. +]]> + </intro> + </strings> + + <tempate src="base-build" /> + + <metadata> + <status>PUBLISHED</status> + <categories>Notification</categories> + <technologies>Android</technologies> + <languages>Kotlin</languages> + <solutions>Mobile</solutions> + <level>INTERMEDIATE</level> + <icon>screenshots/icon-web.png</icon> + <screenshots> + <img>screenshots/main.png</img> + <img>screenshots/chat.png</img> + </screenshots> + <api_refs> + <android>android.app.Notification.BubbleMetadata</android> + </api_refs> + <description> + This sample demonstrates how to use Bubbles API to show notifications as bubbles. + </description> + <intro> +<![CDATA[ +### API Usage + +In order to show a notification as a bubble, call [setBubbleMetadata][1] and set metadata to the +Notification.Builder. [Notification.BubbleMetadata.Builder] can be used to create the metadata. You +can set the icon and the desired height of the bubble. The notification has to be in a notification +channel with [IMPORTANCE_HIGH][2] in order to prompt the user to allow bubbles. + +When the bubble is clicked, it expands to a small window. This is called an expanded bubble. An +expanded bubble is an Activity. Use [setIntent][3] to specify an Activity to be lauched as the +expanded bubble. The Activity must be [resizeable][4] and [embedded][5]. It also has to be able to +launch as multiple instances, so set [documentLaunchMode][6] to "always". + +You might want to provide a feature to let users explicitly show an expanded bubble when they +perform some actions, e.g. tapping on a button to show content in a bubble. For this, call +[setAutoExpandBubble][7] on BubbleMetadata.Builder. It may also make sense to suppress the initial +notification using [setSuppressInitialNotification][8] in this situation. These flags work only when +your app is in the foreground. *This feature does not yet work in Android Q Beta 2.* + +Bubbles can also be shown explicitly when the app is in the foreground. + +[1]: https://developer.android.com/reference/android/app/Notification.Builder.html#setBubbleMetadata +[2]: https://developer.android.com/reference/android/app/NotificationManager.html#IMPORTANCE_HIGH +[3]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setIntent +[4]: https://developer.android.com/guide/topics/manifest/activity-element.html#resizeableActivity +[5]: https://developer.android.com/guide/topics/manifest/activity-element.html#embedded +[6]: https://developer.android.com/guide/topics/manifest/activity-element.html#dlmode +[7]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setAutoExpandBubble +[8]: https://developer.android.com/reference/android/app/Notification.BubbleMetadata.Builder.html#setSuppressInitialNotification + +### When to use Bubbles instead of normal notifications + +Bubbles take up screen real estate and cover other app content. You should only send a notification +as a bubble if it is important enough such as ongoing communications, or if the user has explicitly +requested a bubble for some content. + +### When Bubble is disabled + +Note that the bubble can be disabled by the user. In that case, a bubble notification is shown as a +normal notification. You should always make sure your bubble notification works as a normal +notification as well. + +### Expanded Bubbles + +System UI allows the user to horizontally swipe through expanded bubbles. You should avoid +horizontal scrolling content in your Activity for expanded bubbles. + +### About the sample + +The sample is a dummy chat app. Talk to one of the chat bots, and it will reply back to you in +several seconds. The notification is not shown when the app is in the foreground, so just press back +or home before you get a reply. + +- cat.jpg: Photo by [Erik-Jan Leusink](https://unsplash.com/photos/IbPxGLgJiMI?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/cat?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) +- dog.jpg: Photo by [Lui Peng](https://unsplash.com/photos/ybHtKz5He9Y?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash +- parrot.jpg: Photo by [Nikolay Tchaouchev](https://unsplash.com/photos/jJuq6oNfgRo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash +- sheep.jpg, sheep-full.jpg: Photo by [Luke Stackpoole](https://unsplash.com/photos/sfB_Nw9sggw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash +]]> + </intro> + </metadata> + +</sample> diff --git a/projects.txt b/projects.txt index 84d6fc3d..6fa29dce 100644 --- a/projects.txt +++ b/projects.txt @@ -115,3 +115,4 @@ input/autofill/AutofillFramework views/EmojiCompat ui/fonts/DownloadableFonts wearable/wear/WearComplicationProvidersTestSuite +content/SharingShortcuts diff --git a/security/FingerprintDialog/Application/src/main/Android.mk b/security/FingerprintDialog/Application/src/main/Android.mk index 30420c04..54ec850e 100644 --- a/security/FingerprintDialog/Application/src/main/Android.mk +++ b/security/FingerprintDialog/Application/src/main/Android.mk @@ -14,16 +14,17 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-v7-appcompat \ - android-support-v4 \ - android-support-annotations +LOCAL_USE_AAPT2 := true +LOCAL_STATIC_ANDROID_LIBRARIES := \ + androidx.appcompat_appcompat \ + androidx.legacy_legacy-support-v4 \ + androidx.annotation_annotation LOCAL_MODULE_TAGS := samples LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := FingerprintDialog LOCAL_SDK_VERSION := current LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ - frameworks/support/v7/appcompat/res + prebuilts/sdk/current/support/v7/appcompat/res LOCAL_AAPT_FLAGS := --auto-add-overlay \ --extra-packages android.support.v7.appcompat diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java index e30b4c22..867f6f94 100644 --- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java +++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java @@ -26,9 +26,9 @@ import android.preference.PreferenceManager; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.util.Base64; import android.util.Log; import android.view.Menu; diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java index 4ac88462..5ae75185 100644 --- a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java +++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/SettingsActivity.java @@ -18,7 +18,7 @@ package com.example.android.fingerprintdialog; import android.os.Bundle; import android.preference.PreferenceFragment; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; public class SettingsActivity extends AppCompatActivity { diff --git a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml index 130bc8c7..956f5761 100644 --- a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml +++ b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <android.support.v7.widget.Toolbar + <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" diff --git a/ui/text/README.md b/ui/text/README.md index e6119549..8d08bc8b 100644 --- a/ui/text/README.md +++ b/ui/text/README.md @@ -1,47 +1,19 @@ -Text Styling -============ -These samples shows how to style text on Android using spans, in [Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and in [Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin). +Android Text Samples +==================== +These samples show how to work with text in Android. +Explore the samples +------------------- +The **TextStyling** [Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and [Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin) samples show how to style text using spans. -Introduction ------------- -The difference between [TextStyling-Java](https://github.com/googlesamples/android-text/tree/master/TextStyling-Java) and [TextStyling-Kotlin](https://github.com/googlesamples/android-text/tree/master/TextStyling-Kotlin) is only in the language. They have the same set of features, same class names and similar ways of testing the functionality. -## Features -Parse some hardcoded text and do the following: -* Paragraphs starting with “> ” are transformed into quotes. -* Text enclosed in “```” will be transformed into inline code block. -* Lines starting with “+ ” or “* ” will be transformed into bullet points. -To update the text, modify the value of `R.string.display_text`. -This project is not meant to fully cover the markdown capabilities and has several limitations; for example, quotes do not support nesting other elements. - -## Implementation -The text is parsed in the `Parser.parse` method and the spans are created in the `MarkdownBuilder.markdownToSpans` method. -To see how to apply a span, check out `MarkdownBuilder.buildCodeBlockSpan`. To see how to apply multiple spans on the same string, see `MarkdownBuilder.buildQuoteSpan`. For examples of creating custom spans, see `BulletPointSpan`, `CodeBlockSpan` or `FontSpan`. - -## Testing -Text parsing is tested with JUnit tests in `ParserTest`. Span building is tested via Android JUnit tests, in `MarkdownBuilderTest`. - - -Getting Started ---------------- - -Clone this repository, enter the top level directory and run `./gradlew tasks` -to get an overview of all the tasks available for this project. - -Some important tasks are: - -``` -assembleDebug - Assembles all Debug builds. -installDebug - Installs the Debug build. -connectedAndroidTest - Installs and runs the tests for Debug build on connected -devices. -test - Run all unit tests. -``` +The **RoundedBackground** sample shows how to draw a rounded corner background on a text that extends across one or multiple lines, supporting right-to-left text also. Screenshots ----------- <img src="screenshots/main_activity.png" width="30%" /> +<img src="screenshots/rounded_bg.png" width="30%" /> + Support ------- - Stack Overflow: http://stackoverflow.com/questions/tagged/android-text @@ -72,4 +44,3 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` - diff --git a/ui/text/RoundedBackground-Kotlin/README.md b/ui/text/RoundedBackground-Kotlin/README.md new file mode 100644 index 00000000..8bd96e79 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/README.md @@ -0,0 +1,93 @@ +Drawing a rounded corner background on text +============ + +This sample shows how to draw a **rounded** corner background on text. It supports the following cases: + +* Set the background on **one line** text + +<img src="../screenshots/single.png" width="30%" /> + +* Set the background on text over **two or multiple lines** + +<img src="../screenshots/multi.png" width="30%" /> + +* Set the background on **right-to-left** text + +<img src="../screenshots/rtl.png" width="30%" /> + + +Implementation +--------------- +Depending on the position of the text, we need to draw four different drawables as text backgrounds: + +* Text fits on one line: we only need one drawable +* Text fits on 2 lines: we need drawables for the start and end of the text +* Text spans multiple lines: we need drawables for the start, middle and end of the text + +<img src="../screenshots/lines.png" width="30%" /> + +The four drawables that need to be drawn depending on the position of the text: + +To position the background, we need to: +* Determine whether the text spans multiple lines +* Find the start and end lines +* Find the start and end offset depending on the paragraph direction + +All of these can be computed based on the text Layout. To render the background behind the text we need access to the Canvas. A custom TextView has access to all of the information necessary to position the drawables and render them. + +Our solution involves splitting our problem into 4 parts and creating classes dealing with them individually: +* **Marking the position of the background** is done in the XML resources via Annotation spans and then, in the code, we read them in the `TextRoundedBgHelper` +* Providing the background **drawables as attributes** of the TextView - implemented in `TextRoundedBgAttributeReader` +* **Rendering the drawables** depending on whether the text runs across **one or multiple lines** - `TextRoundedBgHelper` interface and its implementations: `SingleLineRenderer` and `MultiLineRenderer` +* Supporting **custom drawing** on a TextView - `RoundedBgTextView`, a class that extends `AppCompatTextView`, reads the attributes with the help of `TextRoundedBgAttributeReader`, overrides `onDraw` where it uses `TextRoundedBgHelper` to draw the background. + +Getting Started +--------------- + +Clone this repository, enter the top level directory and run `./gradlew tasks` +to get an overview of all the tasks available for this project. + +Some important tasks are: + +``` +assembleDebug - Assembles all Debug builds. +installDebug - Installs the Debug build. +connectedAndroidTest - Installs and runs the tests for Debug build on connected +devices. +test - Run all unit tests. +``` + +Screenshots +----------- +<img src="../screenshots/rounded_bg.png" width="30%" /> + +Support +------- +- Stack Overflow: http://stackoverflow.com/questions/tagged/android-text + +If you've found an error in this sample, please file an issue: +https://github.com/googlesamples/android-text/issues + +Patches are encouraged, and may be submitted by forking this project and +submitting a pull request through GitHub. + +License +-------- +``` +Copyright 2018 The Android Open Source Project + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. +``` diff --git a/ui/text/RoundedBackground-Kotlin/app/build.gradle b/ui/text/RoundedBackground-Kotlin/app/build.gradle new file mode 100644 index 00000000..0b78ce1c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/build.gradle @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 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. + */ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + + +android { + compileSdkVersion rootProject.compileSdkVersion + + defaultConfig { + applicationId "com.android.example.text.styling.roundedbg" + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation project(':lib') + implementation "androidx.appcompat:appcompat:$androidxVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d7429d4b --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.example.text.styling.roundedbg.app"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.RoundedBackground"> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt b/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt new file mode 100644 index 00000000..53ae397c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/java/com/android/example/text/styling/roundedbg/app/MainActivity.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg.app + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +/** + * Sample activity that uses [com.android.example.text.styling.roundedbg.RoundedBgTextView]. + */ +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..888da70e --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector> diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..a5e49139 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector> diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml new file mode 100644 index 00000000..f69e6bf9 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedBg"/> + <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/> + <corners android:radius="@dimen/roundedBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml new file mode 100644 index 00000000..6883c63e --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_left.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedBg"/> + <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/> + <corners android:topLeftRadius="@dimen/roundedTextBorderRadius" + android:bottomLeftRadius="@dimen/roundedTextBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml new file mode 100644 index 00000000..b1714ecd --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_mid.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedBg"/> + <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml new file mode 100644 index 00000000..8066d972 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/drawable/rounded_right.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedBg"/> + <stroke android:width="@dimen/roundedBorderWidth" android:color="@color/roundedBorder"/> + <corners android:topRightRadius="@dimen/roundedBorderRadius" + android:bottomRightRadius="@dimen/roundedBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..b3738cb3 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" + tools:context=".MainActivity"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/ltr" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/ltr_multi" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/ltr_long" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin" + app:roundedTextDrawable="@drawable/rounded" + app:roundedTextDrawableLeft="@drawable/rounded_left" + app:roundedTextDrawableMid="@drawable/rounded_mid" + app:roundedTextDrawableRight="@drawable/rounded_right"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/rtl" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/rtl_multi" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/lang1" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/lang2" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/lang3" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + <View style="@style/Widget.RoundedBackground.Divider" + android:layout_width="match_parent" + android:layout_height="@dimen/dividerHeight"/> + + <com.android.example.text.styling.roundedbg.RoundedBgTextView + android:text="@string/lang4" + style="@style/Widget.RoundedBackground.SampleTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/textViewMargin"/> + + </LinearLayout> + + +</ScrollView> diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..d3441ca6 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..d3441ca6 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..a2f59082 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..1b523998 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..ff10afd6 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..115a4c76 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..dcd3cd80 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..459ca609 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..8ca12fe0 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..8e19b410 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..b824ebdd --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..4c19a13c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..932c9e93 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/colors.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <color name="colorPrimary">#DDD</color> + <color name="colorPrimaryDark">#CCC</color> + <color name="colorAccent">#FF7F50</color> + + <color name="divider">#CCC</color> + + <color name="roundedBg">#FFF176</color> + <color name="roundedBorder">#CABF45</color> +</resources> diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..02814ded --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/dimens.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <dimen name="textViewMargin">8dp</dimen> + <dimen name="dividerHeight">1dp</dimen> + + <dimen name="roundedBorderRadius">6dp</dimen> + <dimen name="roundedBorderWidth">2dp</dimen> +</resources> diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..35cc087d --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/strings.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <string name="app_name">Rounded Multiline Bg</string> + + <!-- Without the quotes at the begining and end Android strips the whitespace and also starts + the annotation at the wrong position. --> + <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string> + <string name="ltr_multi">"this contains <annotation key="rounded">a\nline break</annotation> and continues."</string> + <string name="ltr_long">"this is <annotation key="rounded">a paragraph \nthat would cover more than\ntwo</annotation> lines."</string> + <string name="rtl"> هذا هو "<annotation key="rounded">الحق في</annotation>" الفقرة اليسرى </string> + <string name="rtl_multi"> هذا هو"<annotation key="rounded">حق\n لفقرة</annotation>" اليسار </string> + <string name="lang1">"ဟ <annotation key="rounded">ယ်လို\nဟယ်လို</annotation> ယ်လို"</string> + <string name="lang2">"អ <annotation key="rounded">៊ីតាលី\nអ៊ីតា</annotation> លី""</string> + <string name="lang3">"नमस्ते <annotation key="rounded">दुनिया\nनमस्ते</annotation> दुनिया"</string> + <string name="lang4">"สวัสดี <annotation key="rounded">ชาวโลก\nสวัสดีชาวโ</annotation> ลก"</string> + +</resources>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..1997dad9 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/app/src/main/res/values/styles.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + + <!-- Base application theme. --> + <style name="Theme.RoundedBackground" parent="Theme.AppCompat.Light"> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + + <style name="Widget.RoundedBackground" parent="android:Widget"/> + + <style name="Widget.RoundedBackground.SampleTextView"> + <item name="android:lineSpacingExtra">2dp</item> + <item name="android:lineSpacingMultiplier">1.5</item> + <item name="android:padding">4dp</item> + <item name="android:textSize">18sp</item> + <item name="android:includeFontPadding">true</item> + <item name="roundedTextHorizontalPadding">2dp</item> + <item name="roundedTextVerticalPadding">2dp</item> + </style> + + <style name="Widget.RoundedBackground.Divider"> + <item name="android:background">#CCC</item> + <item name="android:padding">2dp</item> + </style> +</resources> diff --git a/ui/text/RoundedBackground-Kotlin/build.gradle b/ui/text/RoundedBackground-Kotlin/build.gradle new file mode 100644 index 00000000..f6df7566 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/build.gradle @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.2.51' + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0-alpha03' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + // Sdk and tools + minSdkVersion = 15 + targetSdkVersion = 28 + compileSdkVersion = 28 + // App dependencies + androidxVersion = '1.0.0-beta01' + ktxVersion = '1.0.0-alpha1' +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/gradle.properties b/ui/text/RoundedBackground-Kotlin/gradle.properties new file mode 100644 index 00000000..743d692c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 00000000..7a3265ee --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.jar diff --git a/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..37fcd35b --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 11 10:47:22 PDT 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip diff --git a/ui/text/RoundedBackground-Kotlin/gradlew b/ui/text/RoundedBackground-Kotlin/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/ui/text/RoundedBackground-Kotlin/gradlew.bat b/ui/text/RoundedBackground-Kotlin/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/ui/text/RoundedBackground-Kotlin/lib/.gitignore b/ui/text/RoundedBackground-Kotlin/lib/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/ui/text/RoundedBackground-Kotlin/lib/build.gradle b/ui/text/RoundedBackground-Kotlin/lib/build.gradle new file mode 100644 index 00000000..6c499867 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/build.gradle @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 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. + */ +apply plugin: 'com.android.library' + +apply plugin: 'kotlin-android' + +android { + compileSdkVersion rootProject.compileSdkVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation "androidx.core:core-ktx:${ktxVersion}" + implementation "androidx.appcompat:appcompat:$androidxVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml new file mode 100644 index 00000000..43a5eb06 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<manifest + package="com.android.example.text.styling.roundedbg"/> diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt new file mode 100644 index 00000000..f7478eb4 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/LayoutExtensions.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg + +import android.os.Build +import android.text.Layout + +// Extension functions for Layout object + +/** + * Android system default line spacing extra + */ +private const val DEFAULT_LINESPACING_EXTRA = 0f + +/** + * Android system default line spacing multiplier + */ +private const val DEFAULT_LINESPACING_MULTIPLIER = 1f + +/** + * Get the line bottom discarding the line spacing added. + */ +fun Layout.getLineBottomWithoutSpacing(line: Int): Int { + val lineBottom = getLineBottom(line) + val lastLineSpacingNotAdded = Build.VERSION.SDK_INT >= 19 + val isLastLine = line == lineCount - 1 + + val lineBottomWithoutSpacing: Int + val lineSpacingExtra = spacingAdd + val lineSpacingMultiplier = spacingMultiplier + val hasLineSpacing = lineSpacingExtra != DEFAULT_LINESPACING_EXTRA + || lineSpacingMultiplier != DEFAULT_LINESPACING_MULTIPLIER + + if (!hasLineSpacing || isLastLine && lastLineSpacingNotAdded) { + lineBottomWithoutSpacing = lineBottom + } else { + val extra: Float + if (lineSpacingMultiplier.compareTo(DEFAULT_LINESPACING_MULTIPLIER) != 0) { + val lineHeight = getLineHeight(line) + extra = lineHeight - (lineHeight - lineSpacingExtra) / lineSpacingMultiplier + } else { + extra = lineSpacingExtra + } + + lineBottomWithoutSpacing = (lineBottom - extra).toInt() + } + + return lineBottomWithoutSpacing +} + +/** + * Get the line height of a line. + */ +fun Layout.getLineHeight(line: Int): Int { + return getLineTop(line + 1) - getLineTop(line) +} + +/** + * Returns the top of the Layout after removing the extra padding applied by the Layout. + */ +fun Layout.getLineTopWithoutPadding(line: Int): Int { + var lineTop = getLineTop(line) + if (line == 0) { + lineTop -= topPadding + } + return lineTop +} + +/** + * Returns the bottom of the Layout after removing the extra padding applied by the Layout. + */ +fun Layout.getLineBottomWithoutPadding(line: Int): Int { + var lineBottom = getLineBottomWithoutSpacing(line) + if (line == lineCount - 1) { + lineBottom -= bottomPadding + } + return lineBottom +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt new file mode 100644 index 00000000..9f0d2881 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/RoundedBgTextView.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg + +import android.content.Context +import android.graphics.Canvas +import android.text.Spanned +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.graphics.withTranslation + +/** + * A TextView that can draw rounded background to the portions of the text. See + * [TextRoundedBgHelper] for more information. + * + * See [TextRoundedBgAttributeReader] for supported attributes. + */ +class RoundedBgTextView : AppCompatTextView { + + private val textRoundedBgHelper: TextRoundedBgHelper + + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = android.R.attr.textViewStyle + ) : super(context, attrs, defStyleAttr) { + val attributeReader = TextRoundedBgAttributeReader(context, attrs) + textRoundedBgHelper = TextRoundedBgHelper( + horizontalPadding = attributeReader.horizontalPadding, + verticalPadding = attributeReader.verticalPadding, + drawable = attributeReader.drawable, + drawableLeft = attributeReader.drawableLeft, + drawableMid = attributeReader.drawableMid, + drawableRight = attributeReader.drawableRight + ) + } + + override fun onDraw(canvas: Canvas) { + // need to draw bg first so that text can be on top during super.onDraw() + if (text is Spanned && layout != null) { + canvas.withTranslation(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat()) { + textRoundedBgHelper.draw(canvas, text as Spanned, layout) + } + } + super.onDraw(canvas) + } +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt new file mode 100644 index 00000000..5ecc41d9 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgAttributeReader.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.core.content.res.getDrawableOrThrow + +/** + * Reads default attributes that [TextRoundedBgHelper] needs from resources. The attributes read + * are: + * + * - chHorizontalPadding: the padding to be applied to left & right of the background + * - chVerticalPadding: the padding to be applied to top & bottom of the background + * - chDrawable: the drawable used to draw the background + * - chDrawableLeft: the drawable used to draw left edge of the background + * - chDrawableMid: the drawable used to draw for whole line + * - chDrawableRight: the drawable used to draw right edge of the background + */ +class TextRoundedBgAttributeReader(context: Context, attrs: AttributeSet?) { + + val horizontalPadding: Int + val verticalPadding: Int + val drawable: Drawable + val drawableLeft: Drawable + val drawableMid: Drawable + val drawableRight: Drawable + + init { + val typedArray = context.obtainStyledAttributes( + attrs, + R.styleable.TextRoundedBgHelper, + 0, + R.style.RoundedBgTextView + ) + horizontalPadding = typedArray.getDimensionPixelSize( + R.styleable.TextRoundedBgHelper_roundedTextHorizontalPadding, + 0 + ) + verticalPadding = typedArray.getDimensionPixelSize( + R.styleable.TextRoundedBgHelper_roundedTextVerticalPadding, + 0 + ) + drawable = typedArray.getDrawableOrThrow( + R.styleable.TextRoundedBgHelper_roundedTextDrawable + ) + drawableLeft = typedArray.getDrawableOrThrow( + R.styleable.TextRoundedBgHelper_roundedTextDrawableLeft + ) + drawableMid = typedArray.getDrawableOrThrow( + R.styleable.TextRoundedBgHelper_roundedTextDrawableMid + ) + drawableRight = typedArray.getDrawableOrThrow( + R.styleable.TextRoundedBgHelper_roundedTextDrawableRight + ) + typedArray.recycle() + } +} diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt new file mode 100644 index 00000000..262a5fe9 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgHelper.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg + +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.text.Annotation +import android.text.Layout +import android.text.Spanned + +/** + * Helper class to draw multi-line rounded background to certain parts of a text. The start/end + * positions of the backgrounds are annotated with [android.text.Annotation] class. Each annotation + * should have the annotation key set to **rounded**. + * + * i.e.: + * ``` + * <!--without the quotes at the begining and end Android strips the whitespace and also starts + * the annotation at the wrong position--> + * <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string> + * ``` + * + * **Note:** BiDi text is not supported. + * + * @param horizontalPadding the padding to be applied to left & right of the background + * @param verticalPadding the padding to be applied to top & bottom of the background + * @param drawable the drawable used to draw the background + * @param drawableLeft the drawable used to draw left edge of the background + * @param drawableMid the drawable used to draw for whole line + * @param drawableRight the drawable used to draw right edge of the background + */ +class TextRoundedBgHelper( + val horizontalPadding: Int, + verticalPadding: Int, + drawable: Drawable, + drawableLeft: Drawable, + drawableMid: Drawable, + drawableRight: Drawable +) { + + private val singleLineRenderer: TextRoundedBgRenderer by lazy { + SingleLineRenderer( + horizontalPadding = horizontalPadding, + verticalPadding = verticalPadding, + drawable = drawable + ) + } + + private val multiLineRenderer: TextRoundedBgRenderer by lazy { + MultiLineRenderer( + horizontalPadding = horizontalPadding, + verticalPadding = verticalPadding, + drawableLeft = drawableLeft, + drawableMid = drawableMid, + drawableRight = drawableRight + ) + } + + /** + * Call this function during onDraw of another widget such as TextView. + * + * @param canvas Canvas to draw onto + * @param text + * @param layout Layout that contains the text + */ + fun draw(canvas: Canvas, text: Spanned, layout: Layout) { + // ideally the calculations here should be cached since they are not cheap. However, proper + // invalidation of the cache is required whenever anything related to text has changed. + val spans = text.getSpans(0, text.length, Annotation::class.java) + spans.forEach { span -> + if (span.value.equals("rounded")) { + val spanStart = text.getSpanStart(span) + val spanEnd = text.getSpanEnd(span) + val startLine = layout.getLineForOffset(spanStart) + val endLine = layout.getLineForOffset(spanEnd) + + // start can be on the left or on the right depending on the language direction. + val startOffset = (layout.getPrimaryHorizontal(spanStart) + + -1 * layout.getParagraphDirection(startLine) * horizontalPadding).toInt() + // end can be on the left or on the right depending on the language direction. + val endOffset = (layout.getPrimaryHorizontal(spanEnd) + + layout.getParagraphDirection(endLine) * horizontalPadding).toInt() + + val renderer = if (startLine == endLine) singleLineRenderer else multiLineRenderer + renderer.draw(canvas, layout, startLine, endLine, startOffset, endOffset) + } + } + } +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt new file mode 100644 index 00000000..37d814a7 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/java/com/android/example/text/styling/roundedbg/TextRoundedBgRenderer.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 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 com.android.example.text.styling.roundedbg + +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.text.Layout +import kotlin.math.max +import kotlin.math.min + +/** + * Base class for single and multi line rounded background renderers. + * + * @param horizontalPadding the padding to be applied to left & right of the background + * @param verticalPadding the padding to be applied to top & bottom of the background + */ +internal abstract class TextRoundedBgRenderer( + val horizontalPadding: Int, + val verticalPadding: Int +) { + + /** + * Draw the background that starts at the {@code startOffset} and ends at {@code endOffset}. + * + * @param canvas Canvas to draw onto + * @param layout Layout that contains the text + * @param startLine the start line for the background + * @param endLine the end line for the background + * @param startOffset the character offset that the background should start at + * @param endOffset the character offset that the background should end at + */ + abstract fun draw( + canvas: Canvas, + layout: Layout, + startLine: Int, + endLine: Int, + startOffset: Int, + endOffset: Int + ) + + /** + * Get the top offset of the line and add padding into account so that there is a gap between + * top of the background and top of the text. + * + * @param layout Layout object that contains the text + * @param line line number + */ + protected fun getLineTop(layout: Layout, line: Int): Int { + return layout.getLineTopWithoutPadding(line) - verticalPadding + } + + /** + * Get the bottom offset of the line and add padding into account so that there is a gap between + * bottom of the background and bottom of the text. + * + * @param layout Layout object that contains the text + * @param line line number + */ + protected fun getLineBottom(layout: Layout, line: Int): Int { + return layout.getLineBottomWithoutPadding(line) + verticalPadding + } +} + +/** + * Draws the background for text that starts and ends on the same line. + * + * @param horizontalPadding the padding to be applied to left & right of the background + * @param verticalPadding the padding to be applied to top & bottom of the background + * @param drawable the drawable used to draw the background + */ +internal class SingleLineRenderer( + horizontalPadding: Int, + verticalPadding: Int, + val drawable: Drawable +) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) { + + override fun draw( + canvas: Canvas, + layout: Layout, + startLine: Int, + endLine: Int, + startOffset: Int, + endOffset: Int + ) { + val lineTop = getLineTop(layout, startLine) + val lineBottom = getLineBottom(layout, startLine) + // get min of start/end for left, and max of start/end for right since we don't + // the language direction + val left = min(startOffset, endOffset) + val right = max(startOffset, endOffset) + drawable.setBounds(left, lineTop, right, lineBottom) + drawable.draw(canvas) + } +} + +/** + * Draws the background for text that starts and ends on different lines. + * + * @param horizontalPadding the padding to be applied to left & right of the background + * @param verticalPadding the padding to be applied to top & bottom of the background + * @param drawableLeft the drawable used to draw left edge of the background + * @param drawableMid the drawable used to draw for whole line + * @param drawableRight the drawable used to draw right edge of the background + */ +internal class MultiLineRenderer( + horizontalPadding: Int, + verticalPadding: Int, + val drawableLeft: Drawable, + val drawableMid: Drawable, + val drawableRight: Drawable +) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) { + + override fun draw( + canvas: Canvas, + layout: Layout, + startLine: Int, + endLine: Int, + startOffset: Int, + endOffset: Int + ) { + // draw the first line + val paragDir = layout.getParagraphDirection(startLine) + val lineEndOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) { + layout.getLineLeft(startLine) - horizontalPadding + } else { + layout.getLineRight(startLine) + horizontalPadding + }.toInt() + + var lineBottom = getLineBottom(layout, startLine) + var lineTop = getLineTop(layout, startLine) + drawStart(canvas, startOffset, lineTop, lineEndOffset, lineBottom) + + // for the lines in the middle draw the mid drawable + for (line in startLine + 1 until endLine) { + lineTop = getLineTop(layout, line) + lineBottom = getLineBottom(layout, line) + drawableMid.setBounds( + (layout.getLineLeft(line).toInt() - horizontalPadding), + lineTop, + (layout.getLineRight(line).toInt() + horizontalPadding), + lineBottom + ) + drawableMid.draw(canvas) + } + + val lineStartOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) { + layout.getLineRight(startLine) + horizontalPadding + } else { + layout.getLineLeft(startLine) - horizontalPadding + }.toInt() + + // draw the last line + lineBottom = getLineBottom(layout, endLine) + lineTop = getLineTop(layout, endLine) + + drawEnd(canvas, lineStartOffset, lineTop, endOffset, lineBottom) + } + + /** + * Draw the first line of a multiline annotation. Handles LTR/RTL. + * + * @param canvas Canvas to draw onto + * @param start start coordinate for the background + * @param top top coordinate for the background + * @param end end coordinate for the background + * @param bottom bottom coordinate for the background + */ + private fun drawStart(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) { + if (start > end) { + drawableRight.setBounds(end, top, start, bottom) + drawableRight.draw(canvas) + } else { + drawableLeft.setBounds(start, top, end, bottom) + drawableLeft.draw(canvas) + } + } + + /** + * Draw the last line of a multiline annotation. Handles LTR/RTL. + * + * @param canvas Canvas to draw onto + * @param start start coordinate for the background + * @param top top position for the background + * @param end end coordinate for the background + * @param bottom bottom coordinate for the background + */ + private fun drawEnd(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) { + if (start > end) { + drawableLeft.setBounds(end, top, start, bottom) + drawableLeft.draw(canvas) + } else { + drawableRight.setBounds(start, top, end, bottom) + drawableRight.draw(canvas) + } + } +}
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml new file mode 100644 index 00000000..0683b1b9 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedTextBg"/> + <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/> + <corners android:radius="@dimen/roundedTextBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml new file mode 100644 index 00000000..5d02c93d --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_left.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedTextBg"/> + <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/> + <corners android:topLeftRadius="@dimen/roundedTextBorderRadius" + android:bottomLeftRadius="@dimen/roundedTextBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml new file mode 100644 index 00000000..e4c2c570 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_mid.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedTextBg"/> + <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml new file mode 100644 index 00000000..cf011c5c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/drawable/rounded_text_bg_right.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/roundedTextBg"/> + <stroke android:width="@dimen/roundedTextBorderWidth" android:color="@color/roundedTextBorder"/> + <corners android:topRightRadius="@dimen/roundedTextBorderRadius" + android:bottomRightRadius="@dimen/roundedTextBorderRadius"/> +</shape>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml new file mode 100644 index 00000000..737ae07c --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/attrs.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <declare-styleable name="TextRoundedBgHelper"> + <attr name="roundedTextHorizontalPadding" format="dimension"/> + <attr name="roundedTextVerticalPadding" format="dimension"/> + <attr name="roundedTextDrawable" format="reference"/> + <attr name="roundedTextDrawableLeft" format="reference"/> + <attr name="roundedTextDrawableMid" format="reference"/> + <attr name="roundedTextDrawableRight" format="reference"/> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml new file mode 100644 index 00000000..c26ac6af --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/colors.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <color name="roundedTextBg">#554fC3f7</color> + <color name="roundedTextBorder">#DD0277BD</color> +</resources> diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml new file mode 100644 index 00000000..dbc7f49e --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + <dimen name="roundedTextBorderRadius">4dp</dimen> + <dimen name="roundedTextBorderWidth">1dp</dimen> +</resources>
\ No newline at end of file diff --git a/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml new file mode 100644 index 00000000..9bffef3d --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/lib/src/main/res/values/styles.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2018 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. + --> +<resources> + + <style name="RoundedBgTextView" parent="@android:style/Widget.TextView"> + <item name="roundedTextHorizontalPadding">2dp</item> + <item name="roundedTextVerticalPadding">2dp</item> + <item name="roundedTextDrawable">@drawable/rounded_text_bg</item> + <item name="roundedTextDrawableLeft">@drawable/rounded_text_bg_left</item> + <item name="roundedTextDrawableMid">@drawable/rounded_text_bg_mid</item> + <item name="roundedTextDrawableRight">@drawable/rounded_text_bg_right</item> + </style> + +</resources> diff --git a/ui/text/RoundedBackground-Kotlin/settings.gradle b/ui/text/RoundedBackground-Kotlin/settings.gradle new file mode 100644 index 00000000..3cbe2493 --- /dev/null +++ b/ui/text/RoundedBackground-Kotlin/settings.gradle @@ -0,0 +1 @@ +include ':app', ':lib' diff --git a/ui/text/screenshots/lines.png b/ui/text/screenshots/lines.png Binary files differnew file mode 100644 index 00000000..dbc86d17 --- /dev/null +++ b/ui/text/screenshots/lines.png diff --git a/ui/text/screenshots/multi.png b/ui/text/screenshots/multi.png Binary files differnew file mode 100644 index 00000000..e37d1e14 --- /dev/null +++ b/ui/text/screenshots/multi.png diff --git a/ui/text/screenshots/rounded_bg.png b/ui/text/screenshots/rounded_bg.png Binary files differnew file mode 100644 index 00000000..1e9acddf --- /dev/null +++ b/ui/text/screenshots/rounded_bg.png diff --git a/ui/text/screenshots/rtl.png b/ui/text/screenshots/rtl.png Binary files differnew file mode 100644 index 00000000..6d8aba31 --- /dev/null +++ b/ui/text/screenshots/rtl.png diff --git a/ui/text/screenshots/single.png b/ui/text/screenshots/single.png Binary files differnew file mode 100644 index 00000000..b264682e --- /dev/null +++ b/ui/text/screenshots/single.png |