/* * 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.AlertDialog; import android.app.Dialog; import android.app.SearchManager; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.res.Configuration; import android.content.Context; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.res.Resources; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo; import android.content.ServiceConnection; import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.os.Vibrator; import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.os.PowerManager; import android.os.Process; import android.os.ServiceManager; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.provider.Settings; import android.util.AttributeSet; import android.util.Slog; import android.util.Log; import android.view.HapticFeedbackConstants; 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 android.widget.Toast; import com.android.internal.widget.multiwaveview.GlowPadView; import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener; import com.android.internal.widget.multiwaveview.TargetDrawable; 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; import com.android.internal.widget.multiwaveview.TargetDrawable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class SearchPanelView extends FrameLayout implements StatusBarPanel, ActivityOptions.OnAnimationStartedListener { private static final int SEARCH_PANEL_HOLD_DURATION = 0; 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 GlowPadView mGlowPadView; private PackageManager mPackageManager; private Resources mResources; private TargetObserver mTargetObserver; private ContentResolver mContentResolver; private List<String> targetList; private int mNavRingAmount; public SearchPanelView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SearchPanelView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; mPackageManager = mContext.getPackageManager(); mResources = mContext.getResources(); mContentResolver = mContext.getContentResolver(); mTargetObserver = new TargetObserver(new Handler()); mNavRingAmount = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SYSTEMUI_NAVRING_AMOUNT, 1); targetList = Arrays.asList(Settings.System.SYSTEMUI_NAVRING_1, Settings.System.SYSTEMUI_NAVRING_2, Settings.System.SYSTEMUI_NAVRING_3, Settings.System.SYSTEMUI_NAVRING_4, Settings.System.SYSTEMUI_NAVRING_5); for (int i = 0; i < targetList.size(); i++) { mContentResolver.registerContentObserver(Settings.System.getUriFor(targetList.get(i)), false, mTargetObserver); } } private void startAssistActivity() { // Close Recent Apps if needed mBar.animateCollapse(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); // Launch Assist Intent intent = SearchManager.getAssistIntent(mContext); if (intent == null) return; try { ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, R.anim.search_launch_enter, R.anim.search_launch_exit, getHandler(), this); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent, opts.toBundle()); } catch (ActivityNotFoundException e) { Slog.w(TAG, "Activity not found for " + intent.getAction()); onAnimationStarted(); } } private boolean launchTarget(int target) { String targetKey; int targetListOffset; if (screenLayout() == Configuration.SCREENLAYOUT_SIZE_LARGE || screenLayout() == Configuration.SCREENLAYOUT_SIZE_XLARGE) { targetListOffset = 0; } else { if (isScreenPortrait() == true) { targetListOffset = 0; } else { targetListOffset = -2; } } if (target <= targetList.size()) { targetKey = Settings.System.getString(mContext.getContentResolver(), targetList.get(target + targetListOffset)); } else { return false; } if (targetKey == null || targetKey.equals("")) { return false; } if (targetKey.startsWith("app:")) { String activity = targetKey.substring(4); ComponentName component = ComponentName.unflattenFromString(activity); /* Try to launch the activity from history, if available.*/ ActivityManager activityManager = (ActivityManager) mContext .getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RecentTaskInfo task : activityManager.getRecentTasks(20, ActivityManager.RECENT_IGNORE_UNAVAILABLE)) { if (task != null && task.origActivity != null && task.origActivity.equals(component)) { if (task.id > 0) { activityManager.moveTaskToFront(task.id, ActivityManager.MOVE_TASK_WITH_HOME); return true; } } } vibrate(); Intent intent = new Intent(); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setComponent(component); intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); return true; } else if (targetKey.equals("screenoff")) { vibrate(); screenOff(); return true; } else if (targetKey.equals("killcurrent")) { vibrate(); killProcess(); return true; } else if (targetKey.equals("screenshot")) { vibrate(); takeScreenshot(); return true; } else if (targetKey.equals("power")) { vibrate(); powerMenu(); return true; } return false; } class GlowPadTriggerListener implements GlowPadView.OnTriggerListener { 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 = mGlowPadView.getResourceIdForTarget(target); boolean launch = launchTarget(target); switch (resId) { case com.android.internal.R.drawable.ic_action_assist_generic: mWaitingForLaunch = true; startAssistActivity(); vibrate(); break; } } public void onFinishFinalAnimation() { } } final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener(); @Override public void onAnimationStarted() { postDelayed(new Runnable() { public void run() { mGlowPadViewListener.mWaitingForLaunch = false; mBar.hideSearchPanel(); } }, SEARCH_PANEL_HOLD_DURATION); } @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 mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); mGlowPadView.setOnTriggerListener(mGlowPadViewListener); setDrawables(); } private void setDrawables() { String target3 = Settings.System.getString(mContext.getContentResolver(), Settings.System.SYSTEMUI_NAVRING_3); if (target3 == null || target3.equals("")) { Settings.System.putString(mContext.getContentResolver(), Settings.System.SYSTEMUI_NAVRING_3, "assist"); } // Custom Targets ArrayList<TargetDrawable> storedDraw = new ArrayList<TargetDrawable>(); int startPosOffset; int endPosOffset; if (screenLayout() == Configuration.SCREENLAYOUT_SIZE_XLARGE) { startPosOffset = 1; endPosOffset = 8; } else if (screenLayout() == Configuration.SCREENLAYOUT_SIZE_LARGE) { if (mNavRingAmount == 4 || mNavRingAmount == 2) { startPosOffset = 0; endPosOffset = 1; } else { startPosOffset = 0; endPosOffset = 3; } } else { if (isScreenPortrait() == true) { if (mNavRingAmount == 4 || mNavRingAmount == 2) { startPosOffset = 0; endPosOffset = 1; } else { startPosOffset = 0; endPosOffset = 3; } } else { if (mNavRingAmount == 4 || mNavRingAmount == 2) { startPosOffset = 2; endPosOffset = 0; } else { startPosOffset = 2; endPosOffset = 1; } } } List<String> targetActivities = Arrays.asList(Settings.System.getString( mContext.getContentResolver(), targetList.get(0)), Settings.System.getString( mContext.getContentResolver(), targetList.get(1)), Settings.System.getString( mContext.getContentResolver(), targetList.get(2)), Settings.System.getString( mContext.getContentResolver(), targetList.get(3)), Settings.System.getString( mContext.getContentResolver(), targetList.get(4))); // Place Holder Targets TargetDrawable cDrawable = new TargetDrawable(mResources, mResources.getDrawable(com.android.internal.R.drawable.ic_lockscreen_camera)); cDrawable.setEnabled(false); // Add Initial Place Holder Targets for (int i = 0; i < startPosOffset; i++) { storedDraw.add(cDrawable); } // Add User Targets for (int i = 0; i < targetActivities.size(); i++) if (targetActivities.get(i) == null || targetActivities.get(i).equals("") || targetActivities.get(i).equals("none")) { storedDraw.add(cDrawable); } else if (targetActivities.get(i).equals("screenshot")) { storedDraw.add(new TargetDrawable(mResources, mResources.getDrawable(R.drawable.ic_navbar_screenshot))); } else if (targetActivities.get(i).equals("killcurrent")) { storedDraw.add(new TargetDrawable(mResources, mResources.getDrawable(R.drawable.ic_navbar_killtask))); } else if (targetActivities.get(i).equals("power")) { storedDraw.add(new TargetDrawable(mResources, mResources.getDrawable(R.drawable.ic_navbar_power))); } else if (targetActivities.get(i).equals("screenoff")) { storedDraw.add(new TargetDrawable(mResources, mResources.getDrawable(R.drawable.ic_navbar_power))); } else if (targetActivities.get(i).equals("assist")) { storedDraw.add(new TargetDrawable(mResources, com.android.internal.R.drawable.ic_action_assist_generic)); } else if (targetActivities.get(i).startsWith("app:")) { try { ActivityInfo activityInfo= mPackageManager.getActivityInfo( ComponentName.unflattenFromString(targetActivities.get(i).substring(4)), PackageManager.GET_RECEIVERS); Drawable activityIcon = activityInfo.loadIcon(mPackageManager); storedDraw.add(new TargetDrawable(mResources, activityIcon)); } catch (Exception e) { /// } } // Add End Place Holder Targets for (int i = 0; i < endPosOffset; i++) { storedDraw.add(cDrawable); } mGlowPadView.setTargetResources(storedDraw); } private void maybeSwapSearchIcon() { Intent intent = SearchManager.getAssistIntent(mContext); if (intent != null) { ComponentName component = intent.getComponent(); if (component == null || !mGlowPadView.replaceTargetDrawablesIfPresent(component, ASSIST_ICON_METADATA_NAME, com.android.internal.R.drawable.ic_action_assist_generic)) { if (DEBUG) Slog.v(TAG, "Couldn't grab icon for component " + component); } } } 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); mGlowPadView.resumeAnimations(); return false; } }; private void vibrate() { Context context = getContext(); if (Settings.System.getInt(context.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0) { Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(mResources.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) { maybeSwapSearchIcon(); 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 mGlowPadView.suspendAnimations(); mGlowPadView.ping(); 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); // mGlowPadView.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; } public boolean isAssistantAvailable() { return SearchManager.getAssistIntent(mContext) != null; } private void screenOff() { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); pm.goToSleep(SystemClock.uptimeMillis() + 1); } private void killProcess() { try { final Intent intent = new Intent(Intent.ACTION_MAIN); String defaultHomePackage = "com.android.launcher"; intent.addCategory(Intent.CATEGORY_HOME); final ResolveInfo res = mContext.getPackageManager().resolveActivity(intent, 0); if (res.activityInfo != null && !res.activityInfo.packageName.equals("android")) { defaultHomePackage = res.activityInfo.packageName; } boolean targetKilled = false; IActivityManager am = ActivityManagerNative.getDefault(); List<RunningAppProcessInfo> apps = am.getRunningAppProcesses(); for (RunningAppProcessInfo appInfo : apps) { int uid = appInfo.uid; // Make sure it's a foreground user application (not system, // root, phone, etc.) if (uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID && appInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { if (appInfo.pkgList != null && (appInfo.pkgList.length > 0)) { for (String pkg : appInfo.pkgList) { if (!pkg.equals("com.android.systemui") && !pkg.equals(defaultHomePackage)) { am.forceStopPackage(pkg); targetKilled = true; break; } } } else { Process.killProcess(appInfo.pid); targetKilled = true; } } if (targetKilled) { Toast.makeText(mContext, R.string.app_killed_message, Toast.LENGTH_SHORT).show(); break; } } } catch (RemoteException remoteException) { // Do nothing; just let it go. } } /** * functions needed for taking screenhots. This leverages the built in ICS * screenshot functionality */ final Object mScreenshotLock = new Object(); ServiceConnection mScreenshotConnection = null; final Runnable mScreenshotTimeout = new Runnable() { @Override public void run() { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; } } } }; private void takeScreenshot() { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; } ComponentName cn = new ComponentName("com.android.systemui", "com.android.systemui.screenshot.TakeScreenshotService"); Intent intent = new Intent(); intent.setComponent(cn); ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mScreenshotLock) { if (mScreenshotConnection != this) { return; } Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, 1); final ServiceConnection myConn = this; Handler h = new Handler(H.getLooper()) { @Override public void handleMessage(Message msg) { synchronized (mScreenshotLock) { if (mScreenshotConnection == myConn) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; H.removeCallbacks(mScreenshotTimeout); } } } }; msg.replyTo = new Messenger(h); msg.arg1 = msg.arg2 = 0; /* * remove for the time being if (mStatusBar != null && * mStatusBar.isVisibleLw()) msg.arg1 = 1; if * (mNavigationBar != null && * mNavigationBar.isVisibleLw()) msg.arg2 = 1; */ /* wait for the dialog box to close */ try { Thread.sleep(1000); } catch (InterruptedException ie) { } /* take the screenshot */ try { messenger.send(msg); } catch (RemoteException e) { } } } @Override public void onServiceDisconnected(ComponentName name) { } }; if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) { mScreenshotConnection = conn; H.postDelayed(mScreenshotTimeout, 10000); } } } private void powerMenu() { final CharSequence[] item_entries = {"Shutdown", "Reboot", "Recovery", "Bootloader"}; final CharSequence[] item_values = {"shutdown", "", "recovery", "bootloader"}; AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle("Power Menu"); builder.setItems(item_entries, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub if (which == 1 || which == 2 || which == 3) { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); pm.reboot((String)item_values[which]); } } }); builder.setNegativeButton("Cancel", new Dialog.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub dialog.dismiss(); } }); builder.create().show(); } private Handler H = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { } } }; public int screenLayout() { final int screenSize = Resources.getSystem().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; return screenSize; } public boolean isScreenPortrait() { return mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; } public class TargetObserver extends ContentObserver { public TargetObserver(Handler handler) { super(handler); } @Override public boolean deliverSelfNotifications() { return super.deliverSelfNotifications(); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); setDrawables(); } } }