This provides the means to replace the assist icon shown in keyguard and the
navigation gesture for assist. It's done by adding metadata called
"com.android.systemui.action_assist_icon" to the activity that handles
android.intent.action.ASSIST. It should point to a StateListDrawable
in that package with the required states. For example:
<meta-data android:name="com.android.systemui.action_assist_icon"
android:resource="@drawable/ic_android_systemui_action_assist" />
And then something like this in drawable/ic_android_systemui_action_assist.xml :
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true"
android:state_active="false"
android:state_focused="false"
android:drawable="@drawable/ic_action_assist_normal" />
<item android:state_enabled="true"
android:state_active="true"
android:state_focused="false"
android:drawable="@drawable/ic_action_assist_activated" />
<item android:state_enabled="true"
android:state_active="false"
android:state_focused="true"
android:drawable="@drawable/ic_action_assist_focused" />
</selector>
Change-Id: Ibc29360e179fed68253ff06a07b1bb2b982d0dab
313 lines
12 KiB
Java
313 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2012 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;
|
|
|
|
import android.animation.LayoutTransition;
|
|
import android.app.ActivityOptions;
|
|
import android.app.SearchManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Resources;
|
|
import android.os.Vibrator;
|
|
import android.provider.Settings;
|
|
import android.util.AttributeSet;
|
|
import android.util.Slog;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewTreeObserver;
|
|
import android.view.ViewTreeObserver.OnPreDrawListener;
|
|
import android.widget.FrameLayout;
|
|
import com.android.internal.widget.multiwaveview.MultiWaveView;
|
|
import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener;
|
|
import com.android.systemui.R;
|
|
import com.android.systemui.recent.StatusBarTouchProxy;
|
|
import com.android.systemui.statusbar.BaseStatusBar;
|
|
import com.android.systemui.statusbar.CommandQueue;
|
|
import com.android.systemui.statusbar.phone.PhoneStatusBar;
|
|
import com.android.systemui.statusbar.tablet.StatusBarPanel;
|
|
import com.android.systemui.statusbar.tablet.TabletStatusBar;
|
|
|
|
public class SearchPanelView extends FrameLayout implements
|
|
StatusBarPanel {
|
|
private static final int SEARCH_PANEL_HOLD_DURATION = 500;
|
|
static final String TAG = "SearchPanelView";
|
|
static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
|
|
private static final String ASSIST_ICON_METADATA_NAME =
|
|
"com.android.systemui.action_assist_icon";
|
|
private final Context mContext;
|
|
private BaseStatusBar mBar;
|
|
private StatusBarTouchProxy mStatusBarTouchProxy;
|
|
|
|
private boolean mShowing;
|
|
private View mSearchTargetsContainer;
|
|
private MultiWaveView mMultiWaveView;
|
|
|
|
public SearchPanelView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
mContext = context;
|
|
mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
|
|
if (mSearchManager == null) {
|
|
Slog.w(TAG, "Search manager not available");
|
|
}
|
|
}
|
|
|
|
private SearchManager mSearchManager;
|
|
|
|
// This code should be the same as that used in LockScreen.java
|
|
public boolean isAssistantAvailable() {
|
|
Intent intent = getAssistIntent();
|
|
return intent == null ? false
|
|
: mContext.getPackageManager().queryIntentActivities(intent,
|
|
PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
|
|
}
|
|
|
|
private Intent getAssistIntent() {
|
|
Intent intent = null;
|
|
SearchManager searchManager = getSearchManager();
|
|
if (searchManager != null) {
|
|
ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
|
|
if (globalSearchActivity != null) {
|
|
intent = new Intent(Intent.ACTION_ASSIST);
|
|
intent.setPackage(globalSearchActivity.getPackageName());
|
|
} else {
|
|
Slog.w(TAG, "No global search activity");
|
|
}
|
|
} else {
|
|
Slog.w(TAG, "No SearchManager");
|
|
}
|
|
return intent;
|
|
}
|
|
|
|
private SearchManager getSearchManager() {
|
|
if (mSearchManager == null) {
|
|
mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
|
|
}
|
|
return mSearchManager;
|
|
}
|
|
|
|
private void startAssistActivity() {
|
|
// Close Recent Apps if needed
|
|
mBar.animateCollapse(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
|
|
// Launch Assist
|
|
Intent intent = getAssistIntent();
|
|
if (intent == null) return;
|
|
try {
|
|
ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
|
|
R.anim.search_launch_enter, R.anim.search_launch_exit);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
mContext.startActivity(intent, opts.toBundle());
|
|
} catch (ActivityNotFoundException e) {
|
|
Slog.w(TAG, "Activity not found for " + intent.getAction());
|
|
}
|
|
}
|
|
|
|
final MultiWaveView.OnTriggerListener mMultiWaveViewListener
|
|
= new MultiWaveView.OnTriggerListener() {
|
|
|
|
private boolean mWaitingForLaunch;
|
|
|
|
public void onGrabbed(View v, int handle) {
|
|
}
|
|
|
|
public void onReleased(View v, int handle) {
|
|
}
|
|
|
|
public void onGrabbedStateChange(View v, int handle) {
|
|
if (!mWaitingForLaunch && OnTriggerListener.NO_HANDLE == handle) {
|
|
mBar.hideSearchPanel();
|
|
}
|
|
}
|
|
|
|
public void onTrigger(View v, final int target) {
|
|
final int resId = mMultiWaveView.getResourceIdForTarget(target);
|
|
switch (resId) {
|
|
case com.android.internal.R.drawable.ic_lockscreen_search:
|
|
mWaitingForLaunch = true;
|
|
startAssistActivity();
|
|
vibrate();
|
|
postDelayed(new Runnable() {
|
|
public void run() {
|
|
mWaitingForLaunch = false;
|
|
mBar.hideSearchPanel();
|
|
}
|
|
}, SEARCH_PANEL_HOLD_DURATION);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void onFinishFinalAnimation() {
|
|
}
|
|
};
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
super.onFinishInflate();
|
|
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
mSearchTargetsContainer = findViewById(R.id.search_panel_container);
|
|
mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
|
|
// TODO: fetch views
|
|
mMultiWaveView = (MultiWaveView) findViewById(R.id.multi_wave_view);
|
|
mMultiWaveView.setOnTriggerListener(mMultiWaveViewListener);
|
|
SearchManager searchManager = getSearchManager();
|
|
if (searchManager != null) {
|
|
ComponentName component = searchManager.getGlobalSearchActivity();
|
|
if (component != null) {
|
|
if (!mMultiWaveView.replaceTargetDrawablesIfPresent(component,
|
|
ASSIST_ICON_METADATA_NAME,
|
|
com.android.internal.R.drawable.ic_lockscreen_search)) {
|
|
Slog.w(TAG, "Couldn't grab icon from component " + component);
|
|
}
|
|
} else {
|
|
Slog.w(TAG, "No search icon specified in component " + component);
|
|
}
|
|
} else {
|
|
Slog.w(TAG, "No SearchManager");
|
|
}
|
|
}
|
|
|
|
private boolean pointInside(int x, int y, View v) {
|
|
final int l = v.getLeft();
|
|
final int r = v.getRight();
|
|
final int t = v.getTop();
|
|
final int b = v.getBottom();
|
|
return x >= l && x < r && y >= t && y < b;
|
|
}
|
|
|
|
public boolean isInContentArea(int x, int y) {
|
|
if (pointInside(x, y, mSearchTargetsContainer)) {
|
|
return true;
|
|
} else if (mStatusBarTouchProxy != null &&
|
|
pointInside(x, y, mStatusBarTouchProxy)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private final OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
|
|
public boolean onPreDraw() {
|
|
getViewTreeObserver().removeOnPreDrawListener(this);
|
|
mMultiWaveView.resumeAnimations();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
private void vibrate() {
|
|
Context context = getContext();
|
|
if (Settings.System.getInt(context.getContentResolver(),
|
|
Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0) {
|
|
Resources res = context.getResources();
|
|
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
|
vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration));
|
|
}
|
|
}
|
|
|
|
public void show(final boolean show, boolean animate) {
|
|
if (!show) {
|
|
final LayoutTransition transitioner = animate ? createLayoutTransitioner() : null;
|
|
((ViewGroup) mSearchTargetsContainer).setLayoutTransition(transitioner);
|
|
}
|
|
mShowing = show;
|
|
if (show) {
|
|
if (getVisibility() != View.VISIBLE) {
|
|
setVisibility(View.VISIBLE);
|
|
// Don't start the animation until we've created the layer, which is done
|
|
// right before we are drawn
|
|
mMultiWaveView.suspendAnimations();
|
|
getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
|
|
vibrate();
|
|
}
|
|
setFocusable(true);
|
|
setFocusableInTouchMode(true);
|
|
requestFocus();
|
|
} else {
|
|
setVisibility(View.INVISIBLE);
|
|
}
|
|
}
|
|
|
|
public void hide(boolean animate) {
|
|
if (mBar != null) {
|
|
// This will indirectly cause show(false, ...) to get called
|
|
mBar.animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
|
|
} else {
|
|
setVisibility(View.INVISIBLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We need to be aligned at the bottom. LinearLayout can't do this, so instead,
|
|
* let LinearLayout do all the hard work, and then shift everything down to the bottom.
|
|
*/
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
super.onLayout(changed, l, t, r, b);
|
|
// setPanelHeight(mSearchTargetsContainer.getHeight());
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchHoverEvent(MotionEvent event) {
|
|
// Ignore hover events outside of this panel bounds since such events
|
|
// generate spurious accessibility events with the panel content when
|
|
// tapping outside of it, thus confusing the user.
|
|
final int x = (int) event.getX();
|
|
final int y = (int) event.getY();
|
|
if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
|
|
return super.dispatchHoverEvent(event);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Whether the panel is showing, or, if it's animating, whether it will be
|
|
* when the animation is done.
|
|
*/
|
|
public boolean isShowing() {
|
|
return mShowing;
|
|
}
|
|
|
|
public void setBar(BaseStatusBar bar) {
|
|
mBar = bar;
|
|
}
|
|
|
|
public void setStatusBarView(final View statusBarView) {
|
|
if (mStatusBarTouchProxy != null) {
|
|
mStatusBarTouchProxy.setStatusBar(statusBarView);
|
|
// mMultiWaveView.setOnTouchListener(new OnTouchListener() {
|
|
// public boolean onTouch(View v, MotionEvent event) {
|
|
// return statusBarView.onTouchEvent(event);
|
|
// }
|
|
// });
|
|
}
|
|
}
|
|
|
|
private LayoutTransition createLayoutTransitioner() {
|
|
LayoutTransition transitioner = new LayoutTransition();
|
|
transitioner.setDuration(200);
|
|
transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
|
|
transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
|
|
return transitioner;
|
|
}
|
|
}
|