Loading packages/SystemUI/res/layout/remote_input.xml 0 → 100644 +46 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2015 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <!-- FrameLayout --> <com.android.systemui.statusbar.policy.RemoteInputView xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@style/systemui_theme_light" android:layout_height="match_parent" android:layout_width="match_parent" android:paddingStart="4dp" android:paddingEnd="2dp" android:paddingBottom="4dp" android:paddingTop="2dp"> <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText" android:id="@+id/remote_input_text" android:layout_height="wrap_content" android:layout_width="match_parent" android:singleLine="true" android:imeOptions="actionSend" /> <ProgressBar android:id="@+id/remote_input_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:visibility="invisible" android:indeterminate="true" style="?android:attr/progressBarStyleHorizontal" /> </com.android.systemui.statusbar.policy.RemoteInputView> packages/SystemUI/res/values/styles.xml +5 −0 Original line number Diff line number Diff line Loading @@ -211,6 +211,11 @@ <item name="android:colorControlActivated">@color/system_accent_color</item> </style> <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorPrimary">@color/system_primary_color</item> <item name="android:colorControlActivated">@color/system_accent_color</item> </style> <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> <item name="android:colorPrimary">@color/system_primary_color</item> <item name="android:colorControlActivated">@color/system_accent_color</item> Loading packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +107 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.app.ActivityManagerNative; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.RemoteInput; import android.app.TaskStackBuilder; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; Loading @@ -49,6 +50,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; Loading Loading @@ -98,6 +100,7 @@ import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; Loading @@ -117,6 +120,9 @@ public abstract class BaseStatusBar extends SystemUI implements // STOPSHIP disable once we resolve b/18102199 private static final boolean NOTIFICATION_CLICK_DEBUG = true; public static final boolean ENABLE_REMOTE_INPUT = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false); protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; Loading Loading @@ -410,6 +416,7 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void run() { for (StatusBarNotification sbn : notifications) { processForRemoteInput(sbn.getNotification()); addNotification(sbn, currentRanking); } } Loading @@ -424,6 +431,7 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.post(new Runnable() { @Override public void run() { processForRemoteInput(sbn.getNotification()); Notification n = sbn.getNotification(); boolean isUpdate = mNotificationData.get(sbn.getKey()) != null || isHeadsUp(sbn.getKey()); Loading Loading @@ -1357,6 +1365,9 @@ public abstract class BaseStatusBar extends SystemUI implements (NotificationContentView) row.findViewById(R.id.expandedPublic); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } PendingIntent contentIntent = sbn.getNotification().contentIntent; if (contentIntent != null) { Loading Loading @@ -1518,9 +1529,103 @@ public abstract class BaseStatusBar extends SystemUI implements } row.setUserLocked(userLocked); row.setStatusBarNotification(entry.notification); applyRemoteInput(entry); return true; } /** * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this * via first-class API. * * TODO: Remove once enough apps specify remote inputs on their own. */ private void processForRemoteInput(Notification n) { if (!ENABLE_REMOTE_INPUT) return; if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && (n.actions == null || n.actions.length == 0)) { Notification.Action viableAction = null; Notification.WearableExtender we = new Notification.WearableExtender(n); List<Notification.Action> actions = we.getActions(); final int numActions = actions.size(); for (int i = 0; i < numActions; i++) { Notification.Action action = actions.get(i); RemoteInput[] remoteInputs = action.getRemoteInputs(); for (RemoteInput ri : action.getRemoteInputs()) { if (ri.getAllowFreeFormInput()) { viableAction = action; break; } } if (viableAction != null) { break; } } if (viableAction != null) { Notification stripped = n.clone(); Notification.Builder.stripForDelivery(stripped); stripped.actions = new Notification.Action[] { viableAction }; stripped.extras.putBoolean("android.rebuild.contentView", true); stripped.contentView = null; stripped.extras.putBoolean("android.rebuild.bigView", true); stripped.bigContentView = null; // Don't create the HUN input view for now because input doesn't work there yet. // TODO: Enable once HUNs can take remote input correctly. if (false) { stripped.extras.putBoolean("android.rebuild.hudView", true); stripped.headsUpContentView = null; } Notification rebuilt = Notification.Builder.rebuild(mContext, stripped); n.actions = rebuilt.actions; n.bigContentView = rebuilt.bigContentView; n.headsUpContentView = rebuilt.headsUpContentView; n.publicVersion = rebuilt.publicVersion; } } } private void applyRemoteInput(final Entry entry) { if (!ENABLE_REMOTE_INPUT) return; RemoteInput remoteInput = null; // See if the notification has exactly one action and this action allows free-form input // TODO: relax restrictions once we support more than one remote input action. Notification.Action[] actions = entry.notification.getNotification().actions; if (actions != null && actions.length == 1) { if (actions[0].getRemoteInputs() != null) { for (RemoteInput ri : actions[0].getRemoteInputs()) { if (ri.getAllowFreeFormInput()) { remoteInput = ri; break; } } } } // See if we have somewhere to put that remote input ViewGroup actionContainer = null; if (remoteInput != null && entry.expandedBig != null) { View actionContainerCandidate = entry.expandedBig .findViewById(com.android.internal.R.id.actions); if (actionContainerCandidate instanceof ViewGroup) { actionContainer = (ViewGroup) actionContainerCandidate; } } if (actionContainer != null) { actionContainer.removeAllViews(); actionContainer.addView( RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput)); } } public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) { return new NotificationClicker(intent, notificationKey); } Loading Loading @@ -2037,6 +2142,8 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setStatusBarNotification(notification); entry.row.notifyContentUpdated(); entry.row.resetHeight(); applyRemoteInput(entry); } protected void notifyHeadsUpScreenOn(boolean screenOn) { Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -115,7 +115,8 @@ public class StatusBarWindowManager { private void applyFocusableFlag(State state) { if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput && state.bouncerShowing) { && state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) { Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.policy; import com.android.systemui.R; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; /** * Host for the remote input. */ public class RemoteInputView extends FrameLayout implements View.OnClickListener { private static final String TAG = "RemoteInput"; private RemoteEditText mEditText; private ProgressBar mProgressBar; private PendingIntent mPendingIntent; private RemoteInput mRemoteInput; private Notification.Action mAction; public RemoteInputView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress); mEditText = (RemoteEditText) getChildAt(0); mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // Check if this was the result of hitting the enter key final boolean isSoftImeEvent = event == null && (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_SEND); final boolean isKeyboardEnterKey = event != null && KeyEvent.isConfirmKey(event.getKeyCode()) && event.getAction() == KeyEvent.ACTION_DOWN; if (isSoftImeEvent || isKeyboardEnterKey) { sendRemoteInput(); return true; } return false; } }); mEditText.setOnClickListener(this); mEditText.setInnerFocusable(false); } private void sendRemoteInput() { Bundle results = new Bundle(); results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); Intent fillInIntent = new Intent(); RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent, results); mEditText.setEnabled(false); mProgressBar.setVisibility(VISIBLE); try { mPendingIntent.send(mContext, 0, fillInIntent); } catch (PendingIntent.CanceledException e) { Log.i(TAG, "Unable to send remote input result", e); } } public static RemoteInputView inflate(Context context, ViewGroup root, Notification.Action action, RemoteInput remoteInput) { RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); v.mEditText.setHint(action.title); v.mPendingIntent = action.actionIntent; v.mRemoteInput = remoteInput; v.mAction = action; return v; } @Override public void onClick(View v) { if (v == mEditText) { if (!mEditText.isFocusable()) { mEditText.setInnerFocusable(true); InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.viewClicked(mEditText); imm.showSoftInput(mEditText, 0); } } } } /** * An EditText that changes appearance based on whether it's focusable and becomes * un-focusable whenever the user navigates away from it or it becomes invisible. */ public static class RemoteEditText extends EditText { private final Drawable mBackground; public RemoteEditText(Context context, AttributeSet attrs) { super(context, attrs); mBackground = getBackground(); } private void defocusIfNeeded() { if (isFocusable() && isEnabled()) { setInnerFocusable(false); } } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (!isShown()) { defocusIfNeeded(); } } @Override protected void onFocusLost() { super.onFocusLost(); defocusIfNeeded(); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { defocusIfNeeded(); } return super.onKeyPreIme(keyCode, event); } void setInnerFocusable(boolean focusable) { setFocusableInTouchMode(focusable); setFocusable(focusable); setCursorVisible(focusable); if (focusable) { requestFocus(); setBackground(mBackground); } else { setBackground(null); } } } } Loading
packages/SystemUI/res/layout/remote_input.xml 0 → 100644 +46 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2015 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <!-- FrameLayout --> <com.android.systemui.statusbar.policy.RemoteInputView xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@style/systemui_theme_light" android:layout_height="match_parent" android:layout_width="match_parent" android:paddingStart="4dp" android:paddingEnd="2dp" android:paddingBottom="4dp" android:paddingTop="2dp"> <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText" android:id="@+id/remote_input_text" android:layout_height="wrap_content" android:layout_width="match_parent" android:singleLine="true" android:imeOptions="actionSend" /> <ProgressBar android:id="@+id/remote_input_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:visibility="invisible" android:indeterminate="true" style="?android:attr/progressBarStyleHorizontal" /> </com.android.systemui.statusbar.policy.RemoteInputView>
packages/SystemUI/res/values/styles.xml +5 −0 Original line number Diff line number Diff line Loading @@ -211,6 +211,11 @@ <item name="android:colorControlActivated">@color/system_accent_color</item> </style> <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorPrimary">@color/system_primary_color</item> <item name="android:colorControlActivated">@color/system_accent_color</item> </style> <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> <item name="android:colorPrimary">@color/system_primary_color</item> <item name="android:colorControlActivated">@color/system_accent_color</item> Loading
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +107 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.app.ActivityManagerNative; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.RemoteInput; import android.app.TaskStackBuilder; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; Loading @@ -49,6 +50,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; Loading Loading @@ -98,6 +100,7 @@ import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.HeadsUpNotificationView; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; Loading @@ -117,6 +120,9 @@ public abstract class BaseStatusBar extends SystemUI implements // STOPSHIP disable once we resolve b/18102199 private static final boolean NOTIFICATION_CLICK_DEBUG = true; public static final boolean ENABLE_REMOTE_INPUT = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false); protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; Loading Loading @@ -410,6 +416,7 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void run() { for (StatusBarNotification sbn : notifications) { processForRemoteInput(sbn.getNotification()); addNotification(sbn, currentRanking); } } Loading @@ -424,6 +431,7 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.post(new Runnable() { @Override public void run() { processForRemoteInput(sbn.getNotification()); Notification n = sbn.getNotification(); boolean isUpdate = mNotificationData.get(sbn.getKey()) != null || isHeadsUp(sbn.getKey()); Loading Loading @@ -1357,6 +1365,9 @@ public abstract class BaseStatusBar extends SystemUI implements (NotificationContentView) row.findViewById(R.id.expandedPublic); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } PendingIntent contentIntent = sbn.getNotification().contentIntent; if (contentIntent != null) { Loading Loading @@ -1518,9 +1529,103 @@ public abstract class BaseStatusBar extends SystemUI implements } row.setUserLocked(userLocked); row.setStatusBarNotification(entry.notification); applyRemoteInput(entry); return true; } /** * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this * via first-class API. * * TODO: Remove once enough apps specify remote inputs on their own. */ private void processForRemoteInput(Notification n) { if (!ENABLE_REMOTE_INPUT) return; if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && (n.actions == null || n.actions.length == 0)) { Notification.Action viableAction = null; Notification.WearableExtender we = new Notification.WearableExtender(n); List<Notification.Action> actions = we.getActions(); final int numActions = actions.size(); for (int i = 0; i < numActions; i++) { Notification.Action action = actions.get(i); RemoteInput[] remoteInputs = action.getRemoteInputs(); for (RemoteInput ri : action.getRemoteInputs()) { if (ri.getAllowFreeFormInput()) { viableAction = action; break; } } if (viableAction != null) { break; } } if (viableAction != null) { Notification stripped = n.clone(); Notification.Builder.stripForDelivery(stripped); stripped.actions = new Notification.Action[] { viableAction }; stripped.extras.putBoolean("android.rebuild.contentView", true); stripped.contentView = null; stripped.extras.putBoolean("android.rebuild.bigView", true); stripped.bigContentView = null; // Don't create the HUN input view for now because input doesn't work there yet. // TODO: Enable once HUNs can take remote input correctly. if (false) { stripped.extras.putBoolean("android.rebuild.hudView", true); stripped.headsUpContentView = null; } Notification rebuilt = Notification.Builder.rebuild(mContext, stripped); n.actions = rebuilt.actions; n.bigContentView = rebuilt.bigContentView; n.headsUpContentView = rebuilt.headsUpContentView; n.publicVersion = rebuilt.publicVersion; } } } private void applyRemoteInput(final Entry entry) { if (!ENABLE_REMOTE_INPUT) return; RemoteInput remoteInput = null; // See if the notification has exactly one action and this action allows free-form input // TODO: relax restrictions once we support more than one remote input action. Notification.Action[] actions = entry.notification.getNotification().actions; if (actions != null && actions.length == 1) { if (actions[0].getRemoteInputs() != null) { for (RemoteInput ri : actions[0].getRemoteInputs()) { if (ri.getAllowFreeFormInput()) { remoteInput = ri; break; } } } } // See if we have somewhere to put that remote input ViewGroup actionContainer = null; if (remoteInput != null && entry.expandedBig != null) { View actionContainerCandidate = entry.expandedBig .findViewById(com.android.internal.R.id.actions); if (actionContainerCandidate instanceof ViewGroup) { actionContainer = (ViewGroup) actionContainerCandidate; } } if (actionContainer != null) { actionContainer.removeAllViews(); actionContainer.addView( RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput)); } } public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) { return new NotificationClicker(intent, notificationKey); } Loading Loading @@ -2037,6 +2142,8 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setStatusBarNotification(notification); entry.row.notifyContentUpdated(); entry.row.resetHeight(); applyRemoteInput(entry); } protected void notifyHeadsUpScreenOn(boolean screenOn) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -115,7 +115,8 @@ public class StatusBarWindowManager { private void applyFocusableFlag(State state) { if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput && state.bouncerShowing) { && state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.policy; import com.android.systemui.R; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; /** * Host for the remote input. */ public class RemoteInputView extends FrameLayout implements View.OnClickListener { private static final String TAG = "RemoteInput"; private RemoteEditText mEditText; private ProgressBar mProgressBar; private PendingIntent mPendingIntent; private RemoteInput mRemoteInput; private Notification.Action mAction; public RemoteInputView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress); mEditText = (RemoteEditText) getChildAt(0); mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // Check if this was the result of hitting the enter key final boolean isSoftImeEvent = event == null && (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_SEND); final boolean isKeyboardEnterKey = event != null && KeyEvent.isConfirmKey(event.getKeyCode()) && event.getAction() == KeyEvent.ACTION_DOWN; if (isSoftImeEvent || isKeyboardEnterKey) { sendRemoteInput(); return true; } return false; } }); mEditText.setOnClickListener(this); mEditText.setInnerFocusable(false); } private void sendRemoteInput() { Bundle results = new Bundle(); results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); Intent fillInIntent = new Intent(); RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent, results); mEditText.setEnabled(false); mProgressBar.setVisibility(VISIBLE); try { mPendingIntent.send(mContext, 0, fillInIntent); } catch (PendingIntent.CanceledException e) { Log.i(TAG, "Unable to send remote input result", e); } } public static RemoteInputView inflate(Context context, ViewGroup root, Notification.Action action, RemoteInput remoteInput) { RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); v.mEditText.setHint(action.title); v.mPendingIntent = action.actionIntent; v.mRemoteInput = remoteInput; v.mAction = action; return v; } @Override public void onClick(View v) { if (v == mEditText) { if (!mEditText.isFocusable()) { mEditText.setInnerFocusable(true); InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.viewClicked(mEditText); imm.showSoftInput(mEditText, 0); } } } } /** * An EditText that changes appearance based on whether it's focusable and becomes * un-focusable whenever the user navigates away from it or it becomes invisible. */ public static class RemoteEditText extends EditText { private final Drawable mBackground; public RemoteEditText(Context context, AttributeSet attrs) { super(context, attrs); mBackground = getBackground(); } private void defocusIfNeeded() { if (isFocusable() && isEnabled()) { setInnerFocusable(false); } } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (!isShown()) { defocusIfNeeded(); } } @Override protected void onFocusLost() { super.onFocusLost(); defocusIfNeeded(); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { defocusIfNeeded(); } return super.onKeyPreIme(keyCode, event); } void setInnerFocusable(boolean focusable) { setFocusableInTouchMode(focusable); setFocusable(focusable); setCursorVisible(focusable); if (focusable) { requestFocus(); setBackground(mBackground); } else { setBackground(null); } } } }