Snap for 10542431 from 6eb4af3f45 to udc-qpr1-release

Change-Id: I376ec7cd39fa4b9dd58898e919a2e220b2797352
This commit is contained in:
Android Build Coastguard Worker
2023-07-22 01:23:23 +00:00
22 changed files with 346 additions and 41 deletions
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
@@ -22,7 +22,7 @@
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:clipToOutline="true"
android:importantForAccessibility="yes"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
@@ -32,7 +32,7 @@
android:layout_width="wrap_content"
android:gravity="center"
android:textColor="?androidprv:attr/textColorOnAccent"
android:drawableEnd="@drawable/ic_split_horizontal"
android:drawableEnd="@drawable/ic_split_exit"
android:drawablePadding="@dimen/split_instructions_drawable_padding"
android:text="@string/toast_split_select_app" />
</com.android.quickstep.views.SplitInstructionsView>
+2 -1
View File
@@ -24,7 +24,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
+2 -1
View File
@@ -25,7 +25,8 @@
android:clipToOutline="true"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<View
android:id="@+id/background"
+2 -1
View File
@@ -29,7 +29,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
+2 -1
View File
@@ -26,7 +26,8 @@
-->
<declare-styleable name="TaskView">
<!-- Border color for a keyboard quick switch task views -->
<attr name="borderColor" format="color" />
<attr name="focusBorderColor" format="color" />
<attr name="hoverBorderColor" format="color" />
</declare-styleable>
<!--
+2
View File
@@ -38,6 +38,8 @@
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<!-- The icon size for the focused task, placed in center of touch target -->
<dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
<!-- The border width shown when task is hovered -->
<dimen name="task_hover_border_width">4dp</dimen>
<!-- The space under the focused task icon -->
<dimen name="overview_task_margin">16dp</dimen>
<!-- The horizontal space between tasks -->
@@ -158,6 +158,7 @@ import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -477,6 +478,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
});
}
/** Dump debug logs to bug report. */
public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {}
/**
* Content is everything on screen except the background and the floating view (if any).
*
@@ -80,7 +80,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
setWillNotDraw(false);
mBorderColor = ta.getColor(
R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR);
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
ta.recycle();
}
@@ -164,7 +164,7 @@ public final class TaskbarAllAppsController {
cleanUpOverlay();
});
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
mOverlayContext, mSlideInView, mControllers);
mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
viewController.show(animate);
mAppsView = mOverlayContext.getAppsView();
@@ -17,6 +17,8 @@ package com.android.launcher3.taskbar.allapps;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
@@ -63,14 +65,23 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
}
mIsOpen = true;
attachToContainer();
mAllAppsCallbacks.onAllAppsTransitionStart(true);
if (animate) {
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(EMPHASIZED);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator.removeListener(this);
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
}
});
mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
} else {
mTranslationShift = TRANSLATION_SHIFT_OPENED;
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
}
}
@@ -81,9 +92,18 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
@Override
protected void handleClose(boolean animate) {
if (mIsOpen) {
mAllAppsCallbacks.onAllAppsTransitionStart(false);
}
handleClose(animate, mAllAppsCallbacks.getCloseDuration());
}
@Override
protected void onCloseComplete() {
mAllAppsCallbacks.onAllAppsTransitionEnd(false);
super.onCloseComplete();
}
@Override
protected Interpolator getIdleInterpolator() {
return EMPHASIZED;
@@ -194,4 +214,11 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
protected boolean isEventOverContent(MotionEvent ev) {
return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
}
@Override
public void onBackInvoked() {
if (!mAllAppsCallbacks.handleSearchBackInvoked()) {
super.onBackInvoked();
}
}
}
@@ -19,6 +19,7 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.allapps.AllAppsTransitionListener;
import com.android.launcher3.appprediction.AppsDividerView;
import com.android.launcher3.taskbar.NavbarButtonsViewController;
import com.android.launcher3.taskbar.TaskbarControllers;
@@ -43,7 +44,8 @@ final class TaskbarAllAppsViewController {
TaskbarAllAppsViewController(
TaskbarOverlayContext context,
TaskbarAllAppsSlideInView slideInView,
TaskbarControllers taskbarControllers) {
TaskbarControllers taskbarControllers,
TaskbarSearchSessionController searchSessionController) {
mContext = context;
mSlideInView = slideInView;
@@ -52,7 +54,7 @@ final class TaskbarAllAppsViewController {
mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
mOverlayController = taskbarControllers.taskbarOverlayController;
mSlideInView.init(new TaskbarAllAppsCallbacks());
mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
setUpAppDivider();
setUpTaskbarStashing();
}
@@ -94,7 +96,13 @@ final class TaskbarAllAppsViewController {
});
}
class TaskbarAllAppsCallbacks {
class TaskbarAllAppsCallbacks implements AllAppsTransitionListener {
private final TaskbarSearchSessionController mSearchSessionController;
private TaskbarAllAppsCallbacks(TaskbarSearchSessionController searchSessionController) {
mSearchSessionController = searchSessionController;
}
int getOpenDuration() {
return mOverlayController.getOpenDuration();
}
@@ -102,5 +110,20 @@ final class TaskbarAllAppsViewController {
int getCloseDuration() {
return mOverlayController.getCloseDuration();
}
@Override
public void onAllAppsTransitionStart(boolean toAllApps) {
mSearchSessionController.onAllAppsTransitionStart(toAllApps);
}
@Override
public void onAllAppsTransitionEnd(boolean toAllApps) {
mSearchSessionController.onAllAppsTransitionEnd(toAllApps);
}
/** Invoked on back press, returning {@code true} if the search session handled it. */
boolean handleSearchBackInvoked() {
return mSearchSessionController.handleBackInvoked();
}
}
}
@@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.allapps
import android.content.Context
import android.view.View
import com.android.launcher3.R
import com.android.launcher3.allapps.AllAppsTransitionListener
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition
import com.android.launcher3.model.data.ItemInfo
@@ -26,23 +27,29 @@ import com.android.launcher3.util.ResourceBasedOverride
import com.android.launcher3.util.ResourceBasedOverride.Overrides
/** Stub for managing the Taskbar search session. */
open class TaskbarSearchSessionController : ResourceBasedOverride {
open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransitionListener {
/** Start the search session lifecycle. */
open fun startLifecycle() {}
open fun startLifecycle() = Unit
/** Destroy the search session. */
open fun onDestroy() {}
open fun onDestroy() = Unit
/** Updates the predicted items shown in the zero-state. */
open fun setZeroStatePredictedItems(items: List<ItemInfo>) {}
open fun setZeroStatePredictedItems(items: List<ItemInfo>) = Unit
/** Updates the search suggestions shown in the zero-state. */
open fun setZeroStateSearchSuggestions(items: List<ItemInfo>) {}
open fun setZeroStateSearchSuggestions(items: List<ItemInfo>) = Unit
override fun onAllAppsTransitionStart(toAllApps: Boolean) = Unit
override fun onAllAppsTransitionEnd(toAllApps: Boolean) = Unit
/** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
open fun handleBackInvoked(): Boolean = false
companion object {
@JvmStatic
fun newInstance(context: Context): TaskbarSearchSessionController {
@@ -203,6 +203,8 @@ public class QuickstepLauncher extends Launcher {
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
@@ -1363,5 +1365,8 @@ public class QuickstepLauncher extends Launcher {
if (recentsView != null) {
recentsView.getSplitSelectController().dump(prefix, writer);
}
if (mAppTransitionManager != null) {
mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
}
}
}
@@ -206,7 +206,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
if (mLastAppearedTaskTargets != null) {
for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
if (appearedTarget.taskId != lastTarget.taskId) {
if (lastTarget != null &&
appearedTarget.taskId != lastTarget.taskId) {
mController.removeTaskTarget(lastTarget.taskId);
}
}
@@ -97,7 +97,6 @@ import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
@@ -413,7 +412,9 @@ public class TaskView extends FrameLayout implements Reusable {
private boolean mIsClickableAsLiveTile = true;
@Nullable private final BorderAnimator mBorderAnimator;
@Nullable private final BorderAnimator mFocusBorderAnimator;
@Nullable private final BorderAnimator mHoverBorderAnimator;
public TaskView(Context context) {
this(context, null);
@@ -439,23 +440,40 @@ public class TaskView extends FrameLayout implements Reusable {
boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
setWillNotDraw(!keyboardFocusHighlightEnabled);
boolean willDrawBorder =
keyboardFocusHighlightEnabled || FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get();
setWillNotDraw(!willDrawBorder);
TypedArray ta = context.obtainStyledAttributes(
attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
if (willDrawBorder) {
TypedArray styledAttrs = context.obtainStyledAttributes(
attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
mBorderAnimator = !keyboardFocusHighlightEnabled
? null
: new BorderAnimator(
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderColor= */ ta.getColor(
R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
/* borderAnimationParams= */ new BorderAnimator.SimpleParams(
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ this::updateBorderBounds,
/* targetView= */ this));
ta.recycle();
mFocusBorderAnimator = keyboardFocusHighlightEnabled ? new BorderAnimator(
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR),
/* borderAnimationParams= */ new BorderAnimator.SimpleParams(
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ this::updateBorderBounds,
/* targetView= */ this)) : null;
mHoverBorderAnimator =
FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get() ? new BorderAnimator(
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR),
/* borderAnimationParams= */ new BorderAnimator.SimpleParams(
/* borderWidthPx= */ context.getResources()
.getDimensionPixelSize(R.dimen.task_hover_border_width),
/* boundsBuilder= */ this::updateBorderBounds,
/* targetView= */ this)) : null;
styledAttrs.recycle();
} else {
mFocusBorderAnimator = null;
mHoverBorderAnimator = null;
}
}
protected void updateBorderBounds(Rect bounds) {
@@ -509,16 +527,48 @@ public class TaskView extends FrameLayout implements Reusable {
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (mBorderAnimator != null) {
mBorderAnimator.buildAnimator(gainFocus).start();
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.buildAnimator(gainFocus).start();
}
}
@Override
public boolean onHoverEvent(MotionEvent event) {
if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) {
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
mHoverBorderAnimator.buildAnimator(/* isAppearing= */ true).start();
break;
case MotionEvent.ACTION_HOVER_EXIT:
mHoverBorderAnimator.buildAnimator(/* isAppearing= */ false).start();
break;
default:
break;
}
}
return super.onHoverEvent(event);
}
@Override
public boolean onInterceptHoverEvent(MotionEvent event) {
if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) {
// avoid triggering hover event on child elements which would cause HOVER_EXIT for this
// task view
return true;
} else {
return super.onInterceptHoverEvent(event);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mBorderAnimator != null) {
mBorderAnimator.drawBorder(canvas);
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.drawBorder(canvas);
}
if (mHoverBorderAnimator != null) {
mHoverBorderAnimator.drawBorder(canvas);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="20dp"
android:tint="#000000"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="20dp">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 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.launcher3.allapps;
/**
* An interface for listening to all-apps open-close transition
*/
public interface AllAppsTransitionListener {
/**
* Called when the transition starts
* @param toAllApps {@code true} if this transition is supposed to end in the AppApps UI
*
* @see ActivityAllAppsContainerView
*/
void onAllAppsTransitionStart(boolean toAllApps);
/**
* Called when the transition ends
* @param toAllApps {@code true} if the final state is all-apps
*
* @see ActivityAllAppsContainerView
*/
void onAllAppsTransitionEnd(boolean toAllApps);
}
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2023 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.launcher3.util
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* A utility class to record and log events. Events are stored in a fixed size array and old logs
* are purged as new events come.
*/
class EventLogArray(private val name: String, size: Int) {
companion object {
private const val TYPE_ONE_OFF = 0
private const val TYPE_FLOAT = 1
private const val TYPE_INTEGER = 2
private const val TYPE_BOOL_TRUE = 3
private const val TYPE_BOOL_FALSE = 4
private fun isEntrySame(entry: EventEntry?, type: Int, event: String): Boolean {
return entry != null && entry.type == type && entry.event == event
}
}
private val logs: Array<EventEntry?>
private var nextIndex = 0
init {
logs = arrayOfNulls(size)
}
fun addLog(event: String) {
addLog(TYPE_ONE_OFF, event, 0f)
}
fun addLog(event: String, extras: Int) {
addLog(TYPE_INTEGER, event, extras.toFloat())
}
fun addLog(event: String, extras: Float) {
addLog(TYPE_FLOAT, event, extras)
}
fun addLog(event: String, extras: Boolean) {
addLog(if (extras) TYPE_BOOL_TRUE else TYPE_BOOL_FALSE, event, 0f)
}
private fun addLog(type: Int, event: String, extras: Float) {
// Merge the logs if it's a duplicate
val last = (nextIndex + logs.size - 1) % logs.size
val secondLast = (nextIndex + logs.size - 2) % logs.size
if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
logs[last]!!.update(type, event, extras)
logs[secondLast]!!.duplicateCount++
return
}
if (logs[nextIndex] == null) {
logs[nextIndex] = EventEntry()
}
logs[nextIndex]!!.update(type, event, extras)
nextIndex = (nextIndex + 1) % logs.size
}
fun dump(prefix: String, writer: PrintWriter) {
writer.println("$prefix$name event history:")
val sdf = SimpleDateFormat(" HH:mm:ss.SSSZ ", Locale.US)
val date = Date()
for (i in logs.indices) {
val log = logs[(nextIndex + logs.size - i - 1) % logs.size] ?: continue
date.time = log.time
val msg = StringBuilder(prefix).append(sdf.format(date)).append(log.event)
when (log.type) {
TYPE_BOOL_FALSE -> msg.append(": false")
TYPE_BOOL_TRUE -> msg.append(": true")
TYPE_FLOAT -> msg.append(": ").append(log.extras)
TYPE_INTEGER -> msg.append(": ").append(log.extras.toInt())
else -> {}
}
if (log.duplicateCount > 0) {
msg.append(" & ").append(log.duplicateCount).append(" similar events")
}
writer.println(msg)
}
}
/** A single event entry. */
private class EventEntry {
var type = 0
var event: String? = null
var extras = 0f
var time: Long = 0
var duplicateCount = 0
fun update(type: Int, event: String, extras: Float) {
this.type = type
this.event = event
this.extras = extras
time = System.currentTimeMillis()
duplicateCount = 0
}
}
}
@@ -69,7 +69,10 @@ public class BaseTestingActivity extends Activity implements View.OnClickListene
mView.setBackgroundColor(Color.BLUE);
setContentView(mView);
registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
registerReceiver(
mCommandReceiver,
new IntentFilter(mAction + SUFFIX_COMMAND),
RECEIVER_EXPORTED);
}
protected void addButton(String title, String method) {