├── .gitignore ├── Readme.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── klinker │ │ └── nougatplayground │ │ ├── DynamicShortcutHelper.java │ │ ├── ImageKeyboardEditText.java │ │ ├── MainActivity.java │ │ └── util │ │ ├── DensityUtil.java │ │ └── ImageUtil.java │ └── res │ ├── drawable-hdpi │ └── ic_shortcut.png │ ├── drawable-mdpi │ └── ic_shortcut.png │ ├── drawable-xhdpi │ └── ic_shortcut.png │ ├── drawable-xxhdpi │ └── ic_shortcut.png │ ├── drawable-xxxhdpi │ └── ic_shortcut.png │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── shortcuts.xml ├── artwork ├── app.png └── shortcuts.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea 11 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Nougat Playground 2 | 3 | ![app](artwork/app.png) ![shortcuts](artwork/shortcuts.png) 4 | 5 | Just a demo of some of the Nougat features: 6 | 7 | - Dynamic and Static Launcher Shortcuts 8 | - Round Icon 9 | - Image Keyboard 10 | 11 | ## Round Icon 12 | 13 | This one is easy enough to add, just add the icon to your `res` folder, then add it to your `AndroidManifest.xml`: 14 | 15 | ```xml 16 | 24 | 25 | ... 26 | ``` 27 | 28 | ## Static Launcher Shortcuts 29 | 30 | Fist add the following in the `AndroidManifest.xml`. It should be a child of your launcher activity: 31 | 32 | ```xml 33 | 36 | ``` 37 | 38 | See the [AndroidManifest.xml](/app/src/main/AndroidManifest.xml) for the full example. 39 | 40 | After adding that, create a `shortcuts.xml` file within your `/res/xml/` directory. 41 | 42 | This can be a starting point, but you will need to customize it for your own app, icons, and intents. 43 | 44 | ```xml 45 | 46 | 48 | 49 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | ## Dynamic Launcher Shortcuts 72 | 73 | Google introduced a new [ShortcutManager](https://developer.android.com/reference/android/content/pm/ShortcutManager.html) API for this. 74 | 75 | Specifically calling `ShortcutManager#setDynamicShortcuts` or `ShortcutManager#addDynamicShortcuts` with `List 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/klinker/nougatplayground/DynamicShortcutHelper.java: -------------------------------------------------------------------------------- 1 | package xyz.klinker.nougatplayground; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.ShortcutInfo; 6 | import android.content.pm.ShortcutManager; 7 | import android.graphics.Bitmap; 8 | import android.graphics.Color; 9 | import android.graphics.drawable.Icon; 10 | import android.net.Uri; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Locale; 17 | import java.util.Set; 18 | 19 | import xyz.klinker.nougatplayground.util.DensityUtil; 20 | import xyz.klinker.nougatplayground.util.ImageUtil; 21 | 22 | public class DynamicShortcutHelper { 23 | 24 | private Context context; 25 | private ShortcutManager manager; 26 | 27 | @SuppressWarnings("WrongConstant") 28 | public DynamicShortcutHelper(Context context) { 29 | this.context = context; 30 | manager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); 31 | } 32 | 33 | public void buildDynamicShortcuts(String[] titles) { 34 | buildDynamicShortcuts(Arrays.asList(titles)); 35 | } 36 | 37 | public void buildDynamicShortcuts(List titles) { 38 | List infos = new ArrayList<>(); 39 | 40 | for (String title : titles) { 41 | Intent messenger = new Intent(context, MainActivity.class); 42 | messenger.setAction(Intent.ACTION_VIEW); 43 | 44 | // example of a web link that you can open in your app 45 | // should probably be some kind of ID or something representing the shortcut you clicked 46 | messenger.setData(Uri.parse("https://klinkerapps.com/" + title.toLowerCase(Locale.US).replace(" ", "_"))); 47 | 48 | Set category = new HashSet<>(); 49 | category.add("android.shortcut.conversation"); 50 | 51 | ShortcutInfo info = new ShortcutInfo.Builder(context, title) 52 | .setIntent(messenger) 53 | .setRank(infos.size()) 54 | .setShortLabel(title) 55 | .setCategories(category) 56 | .setIcon(getIcon()) 57 | .build(); 58 | 59 | infos.add(info); 60 | } 61 | 62 | manager.setDynamicShortcuts(infos); 63 | } 64 | 65 | private Icon getIcon() { 66 | Bitmap color = Bitmap.createBitmap(DensityUtil.toDp(context, 148), DensityUtil.toDp(context, 148), Bitmap.Config.RGB_565); 67 | color.eraseColor(Color.BLUE); 68 | color = ImageUtil.clipToCircle(color); 69 | 70 | return Icon.createWithBitmap(color); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/klinker/nougatplayground/ImageKeyboardEditText.java: -------------------------------------------------------------------------------- 1 | package xyz.klinker.nougatplayground; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.v13.view.inputmethod.EditorInfoCompat; 7 | import android.support.v13.view.inputmethod.InputConnectionCompat; 8 | import android.support.v13.view.inputmethod.InputContentInfoCompat; 9 | import android.support.v4.os.BuildCompat; 10 | import android.util.AttributeSet; 11 | import android.view.inputmethod.EditorInfo; 12 | import android.view.inputmethod.InputConnection; 13 | import android.widget.EditText; 14 | 15 | public class ImageKeyboardEditText extends EditText { 16 | 17 | public interface ImageSelectedCallback { 18 | void onImageSelected(Uri content, String mimeType); 19 | } 20 | 21 | // region view constructors 22 | public ImageKeyboardEditText(Context context) { 23 | super(context); 24 | } 25 | 26 | public ImageKeyboardEditText(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | public ImageKeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | } 33 | 34 | public ImageKeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 35 | super(context, attrs, defStyleAttr, defStyleRes); 36 | } 37 | 38 | // endregion 39 | 40 | private ImageSelectedCallback callback; 41 | 42 | public void setImageSelectedCallback(ImageSelectedCallback callback) { 43 | this.callback = callback; 44 | } 45 | 46 | @Override 47 | public InputConnection onCreateInputConnection(EditorInfo attrs) { 48 | InputConnection con = super.onCreateInputConnection(attrs); 49 | EditorInfoCompat.setContentMimeTypes(attrs, new String[] { "image/gif", "image/png" }); 50 | 51 | return InputConnectionCompat.createWrapper(con, attrs, new InputConnectionCompat.OnCommitContentListener() { 52 | @Override 53 | public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { 54 | if (callback != null) { 55 | if (BuildCompat.isAtLeastNMR1() && 56 | (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { 57 | try { 58 | inputContentInfo.requestPermission(); 59 | } catch (Exception e) { 60 | return false; 61 | } 62 | } 63 | 64 | callback.onImageSelected( 65 | inputContentInfo.getContentUri(), 66 | inputContentInfo.getDescription().getMimeType(0) 67 | ); 68 | 69 | return true; 70 | } else { 71 | return false; 72 | } 73 | } 74 | }); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/klinker/nougatplayground/MainActivity.java: -------------------------------------------------------------------------------- 1 | package xyz.klinker.nougatplayground; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.ImageView; 10 | import android.widget.Toast; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; 14 | 15 | public class MainActivity extends AppCompatActivity implements ImageKeyboardEditText.ImageSelectedCallback { 16 | 17 | private Button writeShortcuts; 18 | private ImageKeyboardEditText editText; 19 | private ImageView gif; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_main); 25 | 26 | writeShortcuts = (Button) findViewById(R.id.write_shortcuts); 27 | editText = (ImageKeyboardEditText) findViewById(R.id.edit_text); 28 | gif = (ImageView) findViewById(R.id.gif_iv); 29 | 30 | editText.setImageSelectedCallback(this); 31 | 32 | writeShortcuts.setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View view) { 35 | new DynamicShortcutHelper(MainActivity.this) 36 | .buildDynamicShortcuts(editText.getText().toString().split(", ")); 37 | } 38 | }); 39 | 40 | handleShortcutIntent(getIntent()); 41 | } 42 | 43 | @Override 44 | public void onNewIntent(Intent intent) { 45 | super.onNewIntent(intent); 46 | handleShortcutIntent(intent); 47 | } 48 | 49 | @Override 50 | public void onImageSelected(Uri content, String mimeType) { 51 | Glide.with(this).load(content).into(new GlideDrawableImageViewTarget(gif)); 52 | } 53 | 54 | private void handleShortcutIntent(Intent intent) { 55 | if (intent.getData() != null) { 56 | // handle the URI with the data. You may have different logic than just 57 | // getting the last path segment 58 | String data = getIntent().getData().getLastPathSegment(); 59 | Toast.makeText(this, "Clicked: " + data, Toast.LENGTH_SHORT).show(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/klinker/nougatplayground/util/DensityUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.klinker.nougatplayground.util; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.TypedValue; 6 | 7 | public class DensityUtil { 8 | public static int toDp(Context context, int px) { 9 | return convert(context, px, TypedValue.COMPLEX_UNIT_PX); 10 | } 11 | 12 | private static int convert(Context context, int amount, int conversionUnit) { 13 | if (amount < 0) { 14 | throw new IllegalArgumentException("px should not be less than zero"); 15 | } 16 | 17 | Resources r = context.getResources(); 18 | return (int) TypedValue.applyDimension(conversionUnit, amount, r.getDisplayMetrics()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/klinker/nougatplayground/util/ImageUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.klinker.nougatplayground.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Path; 6 | 7 | public class ImageUtil { 8 | 9 | /** 10 | * Clips a provided bitmap to a circle. 11 | */ 12 | public static Bitmap clipToCircle(Bitmap bitmap) { 13 | if (bitmap == null) { 14 | return null; 15 | } 16 | 17 | final int width = bitmap.getWidth(); 18 | final int height = bitmap.getHeight(); 19 | final Bitmap outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 20 | 21 | final Path path = new Path(); 22 | path.addCircle( 23 | (float) (width / 2), 24 | (float) (height / 2), 25 | (float) Math.min(width, (height / 2)), 26 | Path.Direction.CCW); 27 | 28 | final Canvas canvas = new Canvas(outputBitmap); 29 | canvas.clipPath(path); 30 | canvas.drawBitmap(bitmap, 0, 0, null); 31 | return outputBitmap; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klinker24/nougat-7.1-playground/6a567a68d4c4f204750631e59f232fa7419091fe/app/src/main/res/drawable-hdpi/ic_shortcut.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klinker24/nougat-7.1-playground/6a567a68d4c4f204750631e59f232fa7419091fe/app/src/main/res/drawable-mdpi/ic_shortcut.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klinker24/nougat-7.1-playground/6a567a68d4c4f204750631e59f232fa7419091fe/app/src/main/res/drawable-xhdpi/ic_shortcut.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klinker24/nougat-7.1-playground/6a567a68d4c4f204750631e59f232fa7419091fe/app/src/main/res/drawable-xxhdpi/ic_shortcut.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klinker24/nougat-7.1-playground/6a567a68d4c4f204750631e59f232fa7419091fe/app/src/main/res/drawable-xxxhdpi/ic_shortcut.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 19 | 20 |