docs: Add new samples for N MR1
- AppShortcuts - CommitContentSampleApp - CommitContentSampleIME Change-Id: I3cefc134839f944b1c0c5efc943fb779c7e7ee70
@@ -351,6 +351,9 @@ developers/build/prebuilts/gradle/DirectShare sam
|
||||
developers/build/prebuilts/gradle/MidiScope samples/${PLATFORM_NAME}/media/MidiScope
|
||||
developers/build/prebuilts/gradle/MidiSynth samples/${PLATFORM_NAME}/media/MidiSynth
|
||||
developers/build/prebuilts/gradle/AsymmetricFingerprintDialog samples/${PLATFORM_NAME}/security/AsymmetricFingerprintDialog
|
||||
developers/build/prebuilts/gradle/AppShortcuts samples/${PLATFORM_NAME}/system/AppShortcuts
|
||||
developers/build/prebuilts/gradle/CommitContentSampleApp samples/${PLATFORM_NAME}/input/keyboard/CommitContentSampleApp
|
||||
developers/build/prebuilts/gradle/CommitContentSampleIME samples/${PLATFORM_NAME}/input/keyboard/CommitContentSampleIME
|
||||
|
||||
developers/build/prebuilts/androidtv samples/${PLATFORM_NAME}/androidtv
|
||||
|
||||
|
||||
43
samples/browseable/AppShortcuts/AndroidManifest.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.example.android.appshortcuts">
|
||||
|
||||
<uses-sdk android:minSdkVersion="25" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/app"
|
||||
android:resizeableActivity="true">
|
||||
|
||||
<activity android:name="com.example.android.appshortcuts.Main">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
|
||||
</activity>
|
||||
<receiver android:name="com.example.android.appshortcuts.MyReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
13
samples/browseable/AppShortcuts/_index.jd
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
page.tags="AppShortcuts"
|
||||
sample.group=System
|
||||
@jd:body
|
||||
|
||||
<p>
|
||||
|
||||
This sample demonstrates how to use the Launcher Shortcuts API introduced in API 25.
|
||||
This API allows an application to define a set of Intents which are displayed as
|
||||
when a user long-presses on the app's launcher icon. Examples are given for
|
||||
registering both links both statically in XML, as well as dynamically at runtime.
|
||||
|
||||
</p>
|
||||
BIN
samples/browseable/AppShortcuts/res/drawable-nodpi/add.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
samples/browseable/AppShortcuts/res/drawable-nodpi/app.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
samples/browseable/AppShortcuts/res/drawable-nodpi/link.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
62
samples/browseable/AppShortcuts/res/layout/list_item.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dip"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#000000"
|
||||
android:textSize="16sp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/line2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#444444"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/remove"
|
||||
android:text="@string/remove_shortcut"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:visibility="visible"
|
||||
style="@android:style/Widget.Material.Button.Borderless"/>
|
||||
<Button
|
||||
android:id="@+id/disable"
|
||||
android:text="@string/disable_shortcut"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:visibility="visible"
|
||||
style="@android:style/Widget.Material.Button.Borderless"/>
|
||||
</LinearLayout>
|
||||
41
samples/browseable/AppShortcuts/res/layout/main.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<Button
|
||||
android:id="@+id/add"
|
||||
android:text="@string/add_new_website"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="onAddPressed"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#444444"
|
||||
android:text="@string/existing_shortcuts"
|
||||
/>
|
||||
<ListView
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1"
|
||||
android:enabled="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
25
samples/browseable/AppShortcuts/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name">アプリのショートカットサンプル</string>
|
||||
<string name="add_new_website">ウェブサイト追加</string>
|
||||
<string name="add_new_website_short">追加</string>
|
||||
<string name="existing_shortcuts">既存のショートカット:</string>
|
||||
<string name="remove_shortcut">削除</string>
|
||||
<string name="disable_shortcut">無効</string>
|
||||
<string name="enable_shortcut">有効</string>
|
||||
</resources>
|
||||
25
samples/browseable/AppShortcuts/res/values/strings.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name">App Shortcuts Sample</string>
|
||||
<string name="add_new_website">Add New Website</string>
|
||||
<string name="add_new_website_short">Add Website</string>
|
||||
<string name="existing_shortcuts">Existing shortcuts:</string>
|
||||
<string name="remove_shortcut">Remove</string>
|
||||
<string name="disable_shortcut">Disable</string>
|
||||
<string name="enable_shortcut">Enable</string>
|
||||
</resources>
|
||||
29
samples/browseable/AppShortcuts/res/xml/shortcuts.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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" >
|
||||
<shortcut
|
||||
android:shortcutId="add_website"
|
||||
android:icon="@drawable/add"
|
||||
android:shortcutShortLabel="@string/add_new_website_short"
|
||||
android:shortcutLongLabel="@string/add_new_website"
|
||||
>
|
||||
<intent
|
||||
android:action="com.example.android.appshortcuts.ADD_WEBSITE"
|
||||
android:targetPackage="com.example.android.appshortcuts"
|
||||
android:targetClass="com.example.android.appshortcuts.Main"
|
||||
/>
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.appshortcuts;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main extends ListActivity implements OnClickListener {
|
||||
static final String TAG = "ShortcutSample";
|
||||
|
||||
private static final String ID_ADD_WEBSITE = "add_website";
|
||||
|
||||
private static final String ACTION_ADD_WEBSITE =
|
||||
"com.example.android.shortcutsample.ADD_WEBSITE";
|
||||
|
||||
private MyAdapter mAdapter;
|
||||
|
||||
private ShortcutHelper mHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.main);
|
||||
|
||||
mHelper = new ShortcutHelper(this);
|
||||
|
||||
mHelper.maybeRestoreAllDynamicShortcuts();
|
||||
|
||||
mHelper.refreshShortcuts(/*force=*/ false);
|
||||
|
||||
if (ACTION_ADD_WEBSITE.equals(getIntent().getAction())) {
|
||||
// Invoked via the manifest shortcut.
|
||||
addWebSite();
|
||||
}
|
||||
|
||||
mAdapter = new MyAdapter(this.getApplicationContext());
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refreshList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the add button.
|
||||
*/
|
||||
public void onAddPressed(View v) {
|
||||
addWebSite();
|
||||
}
|
||||
|
||||
private void addWebSite() {
|
||||
Log.i(TAG, "addWebSite");
|
||||
|
||||
// This is important. This allows the launcher to build a prediction model.
|
||||
mHelper.reportShortcutUsed(ID_ADD_WEBSITE);
|
||||
|
||||
final EditText editUri = new EditText(this);
|
||||
|
||||
editUri.setHint("http://www.android.com/");
|
||||
editUri.setInputType(EditorInfo.TYPE_TEXT_VARIATION_URI);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Add new website")
|
||||
.setMessage("Type URL of a website")
|
||||
.setView(editUri)
|
||||
.setPositiveButton("Add", (dialog, whichButton) -> {
|
||||
final String url = editUri.getText().toString().trim();
|
||||
if (url.length() > 0) {
|
||||
addUriAsync(url);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void addUriAsync(String uri) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mHelper.addWebSiteShortcut(uri);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
refreshList();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void refreshList() {
|
||||
mAdapter.setShortcuts(mHelper.getShortcuts());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final ShortcutInfo shortcut = (ShortcutInfo) ((View) v.getParent()).getTag();
|
||||
|
||||
switch (v.getId()) {
|
||||
case R.id.disable:
|
||||
if (shortcut.isEnabled()) {
|
||||
mHelper.disableShortcut(shortcut);
|
||||
} else {
|
||||
mHelper.enableShortcut(shortcut);
|
||||
}
|
||||
refreshList();
|
||||
break;
|
||||
case R.id.remove:
|
||||
mHelper.removeShortcut(shortcut);
|
||||
refreshList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<ShortcutInfo> EMPTY_LIST = new ArrayList<>();
|
||||
|
||||
private String getType(ShortcutInfo shortcut) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String sep = "";
|
||||
if (shortcut.isDynamic()) {
|
||||
sb.append(sep);
|
||||
sb.append("Dynamic");
|
||||
sep = ", ";
|
||||
}
|
||||
if (shortcut.isPinned()) {
|
||||
sb.append(sep);
|
||||
sb.append("Pinned");
|
||||
sep = ", ";
|
||||
}
|
||||
if (!shortcut.isEnabled()) {
|
||||
sb.append(sep);
|
||||
sb.append("Disabled");
|
||||
sep = ", ";
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private class MyAdapter extends BaseAdapter {
|
||||
private final Context mContext;
|
||||
private final LayoutInflater mInflater;
|
||||
private List<ShortcutInfo> mList = EMPTY_LIST;
|
||||
|
||||
public MyAdapter(Context context) {
|
||||
mContext = context;
|
||||
mInflater = mContext.getSystemService(LayoutInflater.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setShortcuts(List<ShortcutInfo> list) {
|
||||
mList = list;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View view;
|
||||
if (convertView != null) {
|
||||
view = convertView;
|
||||
} else {
|
||||
view = mInflater.inflate(R.layout.list_item, null);
|
||||
}
|
||||
|
||||
bindView(view, position, mList.get(position));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void bindView(View view, int position, ShortcutInfo shortcut) {
|
||||
view.setTag(shortcut);
|
||||
|
||||
final TextView line1 = (TextView) view.findViewById(R.id.line1);
|
||||
final TextView line2 = (TextView) view.findViewById(R.id.line2);
|
||||
|
||||
line1.setText(shortcut.getLongLabel());
|
||||
|
||||
line2.setText(getType(shortcut));
|
||||
|
||||
final Button remove = (Button) view.findViewById(R.id.remove);
|
||||
final Button disable = (Button) view.findViewById(R.id.disable);
|
||||
|
||||
disable.setText(
|
||||
shortcut.isEnabled() ? R.string.disable_shortcut : R.string.enable_shortcut);
|
||||
|
||||
remove.setOnClickListener(Main.this);
|
||||
disable.setOnClickListener(Main.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.appshortcuts;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class MyReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = Main.TAG;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "onReceive: " + intent);
|
||||
if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
|
||||
// Refresh all shortcut to update the labels.
|
||||
// (Right now shortcut labels don't contain localized strings though.)
|
||||
new ShortcutHelper(context).refreshShortcuts(/*force=*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.appshortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public class ShortcutHelper {
|
||||
private static final String TAG = Main.TAG;
|
||||
|
||||
private static final String EXTRA_LAST_REFRESH =
|
||||
"com.example.android.shortcutsample.EXTRA_LAST_REFRESH";
|
||||
|
||||
private static final long REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final ShortcutManager mShortcutManager;
|
||||
|
||||
public ShortcutHelper(Context context) {
|
||||
mContext = context;
|
||||
mShortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
}
|
||||
|
||||
public void maybeRestoreAllDynamicShortcuts() {
|
||||
if (mShortcutManager.getDynamicShortcuts().size() == 0) {
|
||||
// NOTE: If this application is always supposed to have dynamic shortcuts, then publish
|
||||
// them here.
|
||||
// Note when an application is "restored" on a new device, all dynamic shortcuts
|
||||
// will *not* be restored but the pinned shortcuts *will*.
|
||||
}
|
||||
}
|
||||
|
||||
public void reportShortcutUsed(String id) {
|
||||
mShortcutManager.reportShortcutUsed(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this when interacting with ShortcutManager to show consistent error messages.
|
||||
*/
|
||||
private void callShortcutManager(BooleanSupplier r) {
|
||||
try {
|
||||
if (!r.getAsBoolean()) {
|
||||
Utils.showToast(mContext, "Call to ShortcutManager is rate-limited");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Caught Exception", e);
|
||||
Utils.showToast(mContext, "Error while calling ShortcutManager: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all mutable shortcuts from this app self.
|
||||
*/
|
||||
public List<ShortcutInfo> getShortcuts() {
|
||||
// Load mutable dynamic shortcuts and pinned shortcuts and put them into a single list
|
||||
// removing duplicates.
|
||||
|
||||
final List<ShortcutInfo> ret = new ArrayList<>();
|
||||
final HashSet<String> seenKeys = new HashSet<>();
|
||||
|
||||
// Check existing shortcuts shortcuts
|
||||
for (ShortcutInfo shortcut : mShortcutManager.getDynamicShortcuts()) {
|
||||
if (!shortcut.isImmutable()) {
|
||||
ret.add(shortcut);
|
||||
seenKeys.add(shortcut.getId());
|
||||
}
|
||||
}
|
||||
for (ShortcutInfo shortcut : mShortcutManager.getPinnedShortcuts()) {
|
||||
if (!shortcut.isImmutable() && !seenKeys.contains(shortcut.getId())) {
|
||||
ret.add(shortcut);
|
||||
seenKeys.add(shortcut.getId());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity starts. Looks for shortcuts that have been pushed and refreshes
|
||||
* them (but the refresh part isn't implemented yet...).
|
||||
*/
|
||||
public void refreshShortcuts(boolean force) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Log.i(TAG, "refreshingShortcuts...");
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
final long staleThreshold = force ? now : now - REFRESH_INTERVAL_MS;
|
||||
|
||||
// Check all existing dynamic and pinned shortcut, and if their last refresh
|
||||
// time is older than a certain threshold, update them.
|
||||
|
||||
final List<ShortcutInfo> updateList = new ArrayList<>();
|
||||
|
||||
for (ShortcutInfo shortcut : getShortcuts()) {
|
||||
if (shortcut.isImmutable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final PersistableBundle extras = shortcut.getExtras();
|
||||
if (extras != null && extras.getLong(EXTRA_LAST_REFRESH) >= staleThreshold) {
|
||||
// Shortcut still fresh.
|
||||
continue;
|
||||
}
|
||||
Log.i(TAG, "Refreshing shortcut: " + shortcut.getId());
|
||||
|
||||
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(
|
||||
mContext, shortcut.getId());
|
||||
|
||||
setSiteInformation(b, shortcut.getIntent().getData());
|
||||
setExtras(b);
|
||||
|
||||
updateList.add(b.build());
|
||||
}
|
||||
// Call update.
|
||||
if (updateList.size() > 0) {
|
||||
callShortcutManager(() -> mShortcutManager.updateShortcuts(updateList));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private ShortcutInfo createShortcutForUrl(String urlAsString) {
|
||||
Log.i(TAG, "createShortcutForUrl: " + urlAsString);
|
||||
|
||||
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mContext, urlAsString);
|
||||
|
||||
final Uri uri = Uri.parse(urlAsString);
|
||||
b.setIntent(new Intent(Intent.ACTION_VIEW, uri));
|
||||
|
||||
setSiteInformation(b, uri);
|
||||
setExtras(b);
|
||||
|
||||
return b.build();
|
||||
}
|
||||
|
||||
private ShortcutInfo.Builder setSiteInformation(ShortcutInfo.Builder b, Uri uri) {
|
||||
// TODO Get the actual site <title> and use it.
|
||||
// TODO Set the current locale to accept-language to get localized title.
|
||||
b.setShortLabel(uri.getHost());
|
||||
b.setLongLabel(uri.toString());
|
||||
|
||||
Bitmap bmp = fetchFavicon(uri);
|
||||
if (bmp != null) {
|
||||
b.setIcon(Icon.createWithBitmap(bmp));
|
||||
} else {
|
||||
b.setIcon(Icon.createWithResource(mContext, R.drawable.link));
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
private ShortcutInfo.Builder setExtras(ShortcutInfo.Builder b) {
|
||||
final PersistableBundle extras = new PersistableBundle();
|
||||
extras.putLong(EXTRA_LAST_REFRESH, System.currentTimeMillis());
|
||||
b.setExtras(extras);
|
||||
return b;
|
||||
}
|
||||
|
||||
private String normalizeUrl(String urlAsString) {
|
||||
if (urlAsString.startsWith("http://") || urlAsString.startsWith("https://")) {
|
||||
return urlAsString;
|
||||
} else {
|
||||
return "http://" + urlAsString;
|
||||
}
|
||||
}
|
||||
|
||||
public void addWebSiteShortcut(String urlAsString) {
|
||||
final String uriFinal = urlAsString;
|
||||
callShortcutManager(() -> {
|
||||
final ShortcutInfo shortcut = createShortcutForUrl(normalizeUrl(uriFinal));
|
||||
return mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut));
|
||||
});
|
||||
}
|
||||
|
||||
public void removeShortcut(ShortcutInfo shortcut) {
|
||||
mShortcutManager.removeDynamicShortcuts(Arrays.asList(shortcut.getId()));
|
||||
}
|
||||
|
||||
public void disableShortcut(ShortcutInfo shortcut) {
|
||||
mShortcutManager.disableShortcuts(Arrays.asList(shortcut.getId()));
|
||||
}
|
||||
|
||||
public void enableShortcut(ShortcutInfo shortcut) {
|
||||
mShortcutManager.enableShortcuts(Arrays.asList(shortcut.getId()));
|
||||
}
|
||||
|
||||
private Bitmap fetchFavicon(Uri uri) {
|
||||
final Uri iconUri = uri.buildUpon().path("favicon.ico").build();
|
||||
Log.i(TAG, "Fetching favicon from: " + iconUri);
|
||||
|
||||
InputStream is = null;
|
||||
BufferedInputStream bis = null;
|
||||
try
|
||||
{
|
||||
URLConnection conn = new URL(iconUri.toString()).openConnection();
|
||||
conn.connect();
|
||||
is = conn.getInputStream();
|
||||
bis = new BufferedInputStream(is, 8192);
|
||||
return BitmapFactory.decodeStream(bis);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to fetch favicon from " + iconUri, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.appshortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class Utils {
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
public static void showToast(Context context, String message) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.commitcontent.app">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<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>
|
||||
11
samples/browseable/CommitContentSampleApp/_index.jd
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
page.tags="CommitContentSampleApp"
|
||||
sample.group=Input
|
||||
@jd:body
|
||||
|
||||
<p>
|
||||
|
||||
This sample demonstrates how to write an application which accepts rich content
|
||||
(such as images) sent from a keyboard using the Commit Content API.
|
||||
|
||||
</p>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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="match_parent"
|
||||
android:background="@android:color/black">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/commit_content_webview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#77000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="horizontal">
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_column="1"
|
||||
android:gravity="end"
|
||||
android:padding="3dip"
|
||||
android:text="MIME"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_commit_content_mime_types"
|
||||
android:padding="3dip"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_column="1"
|
||||
android:gravity="end"
|
||||
android:padding="3dip"
|
||||
android:text="Label"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_commit_content_label"
|
||||
android:padding="3dip"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_column="1"
|
||||
android:gravity="end"
|
||||
android:padding="3dip"
|
||||
android:text="URI"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_commit_content_content_uri"
|
||||
android:padding="3dip"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_column="1"
|
||||
android:gravity="end"
|
||||
android:padding="3dip"
|
||||
android:text="Link"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_commit_content_link_uri"
|
||||
android:padding="3dip"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:layout_column="1"
|
||||
android:gravity="end"
|
||||
android:padding="3dip"
|
||||
android:text="Flags"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_commit_content_link_flags"
|
||||
android:padding="3dip"
|
||||
android:textColor="@android:color/white" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commit_content_sample_edit_boxes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">CommitContentSampleApp</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.commitcontent.app;
|
||||
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private static final String INPUT_CONTENT_INFO_KEY = "COMMIT_CONTENT_INPUT_CONTENT_INFO";
|
||||
private static final String COMMIT_CONTENT_FLAGS_KEY = "COMMIT_CONTENT_FLAGS";
|
||||
|
||||
private static String TAG = "CommitContentSupport";
|
||||
|
||||
private WebView mWebView;
|
||||
private TextView mLabel;
|
||||
private TextView mContentUri;
|
||||
private TextView mLinkUri;
|
||||
private TextView mMimeTypes;
|
||||
private TextView mFlags;
|
||||
|
||||
private InputContentInfoCompat mCurrentInputContentInfo;
|
||||
private int mCurrentFlags;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.commit_content);
|
||||
|
||||
final LinearLayout layout =
|
||||
(LinearLayout) findViewById(R.id.commit_content_sample_edit_boxes);
|
||||
|
||||
// This declares that the IME cannot commit any content with
|
||||
// InputConnectionCompat#commitContent().
|
||||
layout.addView(createEditTextWithContentMimeTypes(null));
|
||||
|
||||
// This declares that the IME can commit contents with
|
||||
// InputConnectionCompat#commitContent() if they match "image/gif".
|
||||
layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/gif"}));
|
||||
|
||||
// This declares that the IME can commit contents with
|
||||
// InputConnectionCompat#commitContent() if they match "image/png".
|
||||
layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/png"}));
|
||||
|
||||
// This declares that the IME can commit contents with
|
||||
// InputConnectionCompat#commitContent() if they match "image/jpeg".
|
||||
layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/jpeg"}));
|
||||
|
||||
// This declares that the IME can commit contents with
|
||||
// InputConnectionCompat#commitContent() if they match "image/webp".
|
||||
layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/webp"}));
|
||||
|
||||
// This declares that the IME can commit contents with
|
||||
// InputConnectionCompat#commitContent() if they match "image/png", "image/gif",
|
||||
// "image/jpeg", or "image/webp".
|
||||
layout.addView(createEditTextWithContentMimeTypes(
|
||||
new String[]{"image/png", "image/gif", "image/jpeg", "image/webp"}));
|
||||
|
||||
mWebView = (WebView) findViewById(R.id.commit_content_webview);
|
||||
mMimeTypes = (TextView) findViewById(R.id.text_commit_content_mime_types);
|
||||
mLabel = (TextView) findViewById(R.id.text_commit_content_label);
|
||||
mContentUri = (TextView) findViewById(R.id.text_commit_content_content_uri);
|
||||
mLinkUri = (TextView) findViewById(R.id.text_commit_content_link_uri);
|
||||
mFlags = (TextView) findViewById(R.id.text_commit_content_link_flags);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
final InputContentInfoCompat previousInputContentInfo = InputContentInfoCompat.wrap(
|
||||
savedInstanceState.getParcelable(INPUT_CONTENT_INFO_KEY));
|
||||
final int previousFlags = savedInstanceState.getInt(COMMIT_CONTENT_FLAGS_KEY);
|
||||
if (previousInputContentInfo != null) {
|
||||
onCommitContentInternal(previousInputContentInfo, previousFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
|
||||
Bundle opts, String[] contentMimeTypes) {
|
||||
// Clear the temporary permission (if any). See below about why we do this here.
|
||||
try {
|
||||
if (mCurrentInputContentInfo != null) {
|
||||
mCurrentInputContentInfo.releasePermission();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e);
|
||||
} finally {
|
||||
mCurrentInputContentInfo = null;
|
||||
}
|
||||
|
||||
mWebView.loadUrl("about:blank");
|
||||
mMimeTypes.setText("");
|
||||
mContentUri.setText("");
|
||||
mLabel.setText("");
|
||||
mLinkUri.setText("");
|
||||
mFlags.setText("");
|
||||
|
||||
boolean supported = false;
|
||||
for (final String mimeType : contentMimeTypes) {
|
||||
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
|
||||
supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return onCommitContentInternal(inputContentInfo, flags);
|
||||
}
|
||||
|
||||
private boolean onCommitContentInternal(InputContentInfoCompat inputContentInfo, int flags) {
|
||||
if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
|
||||
try {
|
||||
inputContentInfo.requestPermission();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mMimeTypes.setText(
|
||||
Arrays.toString(inputContentInfo.getDescription().filterMimeTypes("*/*")));
|
||||
mContentUri.setText(inputContentInfo.getContentUri().toString());
|
||||
mLabel.setText(inputContentInfo.getDescription().getLabel());
|
||||
Uri linkUri = inputContentInfo.getLinkUri();
|
||||
mLinkUri.setText(linkUri != null ? linkUri.toString() : "null");
|
||||
mFlags.setText(flagsToString(flags));
|
||||
mWebView.loadUrl(inputContentInfo.getContentUri().toString());
|
||||
mWebView.setBackgroundColor(Color.TRANSPARENT);
|
||||
|
||||
// Due to the asynchronous nature of WebView, it is a bit too early to call
|
||||
// inputContentInfo.releasePermission() here. Hence we call IC#releasePermission() when this
|
||||
// method is called next time. Note that calling IC#releasePermission() is just to be a
|
||||
// good citizen. Even if we failed to call that method, the system would eventually revoke
|
||||
// the permission sometime after inputContentInfo object gets garbage-collected.
|
||||
mCurrentInputContentInfo = inputContentInfo;
|
||||
mCurrentFlags = flags;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
if (mCurrentInputContentInfo != null) {
|
||||
savedInstanceState.putParcelable(INPUT_CONTENT_INFO_KEY,
|
||||
(Parcelable) mCurrentInputContentInfo.unwrap());
|
||||
savedInstanceState.putInt(COMMIT_CONTENT_FLAGS_KEY, mCurrentFlags);
|
||||
}
|
||||
mCurrentInputContentInfo = null;
|
||||
mCurrentFlags = 0;
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link EditText} that is configured to specify the given content
|
||||
* MIME types to EditorInfo#contentMimeTypes so that developers can locally test how the current
|
||||
* input method behaves for such content MIME types.
|
||||
*
|
||||
* @param contentMimeTypes A {@link String} array that indicates the supported content MIME
|
||||
* types
|
||||
* @return a new instance of {@link EditText}, which specifies EditorInfo#contentMimeTypes with
|
||||
* the given content MIME types
|
||||
*/
|
||||
private EditText createEditTextWithContentMimeTypes(String[] contentMimeTypes) {
|
||||
final CharSequence hintText;
|
||||
final String[] mimeTypes; // our own copy of contentMimeTypes.
|
||||
if (contentMimeTypes == null || contentMimeTypes.length == 0) {
|
||||
hintText = "MIME: []";
|
||||
mimeTypes = new String[0];
|
||||
} else {
|
||||
hintText = "MIME: " + Arrays.toString(contentMimeTypes);
|
||||
mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length);
|
||||
}
|
||||
EditText exitText = new EditText(this) {
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||
final InputConnection ic = super.onCreateInputConnection(editorInfo);
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
|
||||
final InputConnectionCompat.OnCommitContentListener callback =
|
||||
new InputConnectionCompat.OnCommitContentListener() {
|
||||
@Override
|
||||
public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
|
||||
int flags, Bundle opts) {
|
||||
return MainActivity.this.onCommitContent(
|
||||
inputContentInfo, flags, opts, mimeTypes);
|
||||
}
|
||||
};
|
||||
return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
|
||||
}
|
||||
};
|
||||
exitText.setHint(hintText);
|
||||
exitText.setTextColor(Color.WHITE);
|
||||
exitText.setHintTextColor(Color.WHITE);
|
||||
return exitText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@code flags} specified in {@link InputConnectionCompat#commitContent(
|
||||
* InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle)} to a human readable
|
||||
* string.
|
||||
*
|
||||
* @param flags the 2nd parameter of
|
||||
* {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo,
|
||||
* InputContentInfoCompat, int, Bundle)}
|
||||
* @return a human readable string that corresponds to the given {@code flags}
|
||||
*/
|
||||
private static String flagsToString(int flags) {
|
||||
final ArrayList<String> tokens = new ArrayList<>();
|
||||
if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
|
||||
tokens.add("INPUT_CONTENT_GRANT_READ_URI_PERMISSION");
|
||||
flags &= ~InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
|
||||
}
|
||||
if (flags != 0) {
|
||||
tokens.add("0x" + Integer.toHexString(flags));
|
||||
}
|
||||
return TextUtils.join(" | ", tokens);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.commitcontent.ime">
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<service
|
||||
android:name="com.example.android.commitcontent.ime.ImageKeyboard"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.example.android.commitcontent.ime.inputcontent"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
11
samples/browseable/CommitContentSampleIME/_index.jd
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
page.tags="CommitContentSampleIME"
|
||||
sample.group=Input
|
||||
@jd:body
|
||||
|
||||
<p>
|
||||
|
||||
This sample demonstrates how to write an keyboard which sends rich content
|
||||
(such as images) to text fields using the Commit Content API.
|
||||
|
||||
</p>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 808 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2016, 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">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2016, 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">CommitContentSampleIme</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2016, 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="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2016, 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 xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="my_images" path="images/"/>
|
||||
</paths>
|
||||
19
samples/browseable/CommitContentSampleIME/res/xml/method.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2016, 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.
|
||||
*/
|
||||
-->
|
||||
<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.commitcontent.ime;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RawRes;
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputBinding;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
public class ImageKeyboard extends InputMethodService {
|
||||
|
||||
private static final String TAG = "ImageKeyboard";
|
||||
private static final String AUTHORITY = "com.example.android.supportv13.sampleime.inputcontent";
|
||||
private static final String MIME_TYPE_GIF = "image/gif";
|
||||
private static final String MIME_TYPE_PNG = "image/png";
|
||||
private static final String MIME_TYPE_WEBP = "image/webp";
|
||||
|
||||
private File mPngFile;
|
||||
private File mGifFile;
|
||||
private File mWebpFile;
|
||||
private Button mGifButton;
|
||||
private Button mPngButton;
|
||||
private Button mWebpButton;
|
||||
|
||||
private boolean isCommitContentSupported(
|
||||
@Nullable EditorInfo editorInfo, @NonNull String mimeType) {
|
||||
if (editorInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final InputConnection ic = getCurrentInputConnection();
|
||||
if (ic == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validatePackageName(editorInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String[] supportedMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
|
||||
for (String supportedMimeType : supportedMimeTypes) {
|
||||
if (ClipDescription.compareMimeTypes(mimeType, supportedMimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doCommitContent(@NonNull String description, @NonNull String mimeType,
|
||||
@NonNull File file) {
|
||||
final EditorInfo editorInfo = getCurrentInputEditorInfo();
|
||||
|
||||
// Validate packageName again just in case.
|
||||
if (!validatePackageName(editorInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Uri contentUri = FileProvider.getUriForFile(this, AUTHORITY, file);
|
||||
|
||||
// As you as an IME author are most likely to have to implement your own content provider
|
||||
// to support CommitContent API, it is important to have a clear spec about what
|
||||
// applications are going to be allowed to access the content that your are going to share.
|
||||
final int flag;
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
// On API 25 and later devices, as an analogy of Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
// you can specify InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION to give
|
||||
// a temporary read access to the recipient application without exporting your content
|
||||
// provider.
|
||||
flag = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
|
||||
} else {
|
||||
// On API 24 and prior devices, we cannot rely on
|
||||
// InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION. You as an IME author
|
||||
// need to decide what access control is needed (or not needed) for content URIs that
|
||||
// you are going to expose. This sample uses Context.grantUriPermission(), but you can
|
||||
// implement your own mechanism that satisfies your own requirements.
|
||||
flag = 0;
|
||||
try {
|
||||
// TODO: Use revokeUriPermission to revoke as needed.
|
||||
grantUriPermission(
|
||||
editorInfo.packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} catch (Exception e){
|
||||
Log.e(TAG, "grantUriPermission failed packageName=" + editorInfo.packageName
|
||||
+ " contentUri=" + contentUri, e);
|
||||
}
|
||||
}
|
||||
|
||||
final InputContentInfoCompat inputContentInfoCompat = new InputContentInfoCompat(
|
||||
contentUri,
|
||||
new ClipDescription(description, new String[]{mimeType}),
|
||||
null /* linkUrl */);
|
||||
InputConnectionCompat.commitContent(
|
||||
getCurrentInputConnection(), getCurrentInputEditorInfo(), inputContentInfoCompat,
|
||||
flag, null);
|
||||
}
|
||||
|
||||
private boolean validatePackageName(@Nullable EditorInfo editorInfo) {
|
||||
if (editorInfo == null) {
|
||||
return false;
|
||||
}
|
||||
final String packageName = editorInfo.packageName;
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In Android L MR-1 and prior devices, EditorInfo.packageName is not a reliable identifier
|
||||
// of the target application because:
|
||||
// 1. the system does not verify it [1]
|
||||
// 2. InputMethodManager.startInputInner() had filled EditorInfo.packageName with
|
||||
// view.getContext().getPackageName() [2]
|
||||
// [1]: https://android.googlesource.com/platform/frameworks/base/+/a0f3ad1b5aabe04d9eb1df8bad34124b826ab641
|
||||
// [2]: https://android.googlesource.com/platform/frameworks/base/+/02df328f0cd12f2af87ca96ecf5819c8a3470dc8
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final InputBinding inputBinding = getCurrentInputBinding();
|
||||
if (inputBinding == null) {
|
||||
// Due to b.android.com/225029, it is possible that getCurrentInputBinding() returns
|
||||
// null even after onStartInputView() is called.
|
||||
// TODO: Come up with a way to work around this bug....
|
||||
Log.e(TAG, "inputBinding should not be null here. "
|
||||
+ "You are likely to be hitting b.android.com/225029");
|
||||
return false;
|
||||
}
|
||||
final int packageUid = inputBinding.getUid();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
final AppOpsManager appOpsManager =
|
||||
(AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
|
||||
try {
|
||||
appOpsManager.checkPackage(packageUid, packageName);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final PackageManager packageManager = getPackageManager();
|
||||
final String possiblePackageNames[] = packageManager.getPackagesForUid(packageUid);
|
||||
for (final String possiblePackageName : possiblePackageNames) {
|
||||
if (packageName.equals(possiblePackageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// TODO: Avoid file I/O in the main thread.
|
||||
final File imagesDir = new File(getFilesDir(), "images");
|
||||
imagesDir.mkdirs();
|
||||
mGifFile = getFileForResource(this, R.raw.animated_gif, imagesDir, "image.gif");
|
||||
mPngFile = getFileForResource(this, R.raw.dessert_android, imagesDir, "image.png");
|
||||
mWebpFile = getFileForResource(this, R.raw.animated_webp, imagesDir, "image.webp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateInputView() {
|
||||
mGifButton = new Button(this);
|
||||
mGifButton.setText("Insert GIF");
|
||||
mGifButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ImageKeyboard.this.doCommitContent("A waving flag", MIME_TYPE_GIF, mGifFile);
|
||||
}
|
||||
});
|
||||
|
||||
mPngButton = new Button(this);
|
||||
mPngButton.setText("Insert PNG");
|
||||
mPngButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ImageKeyboard.this.doCommitContent("A droid logo", MIME_TYPE_PNG, mPngFile);
|
||||
}
|
||||
});
|
||||
|
||||
mWebpButton = new Button(this);
|
||||
mWebpButton.setText("Insert WebP");
|
||||
mWebpButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ImageKeyboard.this.doCommitContent(
|
||||
"Android N recovery animation", MIME_TYPE_WEBP, mWebpFile);
|
||||
}
|
||||
});
|
||||
|
||||
final LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.addView(mGifButton);
|
||||
layout.addView(mPngButton);
|
||||
layout.addView(mWebpButton);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEvaluateFullscreenMode() {
|
||||
// In full-screen mode the inserted content is likely to be hidden by the IME. Hence in this
|
||||
// sample we simply disable full-screen mode.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartInputView(EditorInfo info, boolean restarting) {
|
||||
mGifButton.setEnabled(mGifFile != null && isCommitContentSupported(info, MIME_TYPE_GIF));
|
||||
mPngButton.setEnabled(mPngFile != null && isCommitContentSupported(info, MIME_TYPE_PNG));
|
||||
mWebpButton.setEnabled(mWebpFile != null && isCommitContentSupported(info, MIME_TYPE_WEBP));
|
||||
}
|
||||
|
||||
private static File getFileForResource(
|
||||
@NonNull Context context, @RawRes int res, @NonNull File outputDir,
|
||||
@NonNull String filename) {
|
||||
final File outputFile = new File(outputDir, filename);
|
||||
final byte[] buffer = new byte[4096];
|
||||
InputStream resourceReader = null;
|
||||
try {
|
||||
try {
|
||||
resourceReader = context.getResources().openRawResource(res);
|
||||
OutputStream dataWriter = null;
|
||||
try {
|
||||
dataWriter = new FileOutputStream(outputFile);
|
||||
while (true) {
|
||||
final int numRead = resourceReader.read(buffer);
|
||||
if (numRead <= 0) {
|
||||
break;
|
||||
}
|
||||
dataWriter.write(buffer, 0, numRead);
|
||||
}
|
||||
return outputFile;
|
||||
} finally {
|
||||
if (dataWriter != null) {
|
||||
dataWriter.flush();
|
||||
dataWriter.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (resourceReader != null) {
|
||||
resourceReader.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||