Press the Home key while in Home to enable the gestures pad.
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="GesturesActivity"
+ android:label="@string/gestures_activity" />
<!-- Enable system-default search mode for any activity in Home -->
<meta-data
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/btn_circle_disable" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_circle_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_circle_disable_focused" />
+ <item
+ android:drawable="@drawable/btn_circle_disable" />
+</selector>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2009 Romain Guy
+
+ 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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/texture_paper"
+ android:tileMode="repeat" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Note: GesturesPanel is a special implementation that forces the widget
+ to be opaque for performance reasons. Make sure it visually is. -->
+<com.android.launcher.GesturesPanel
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:id="@+id/gestures_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ViewSwitcher
+ android:id="@+id/gestures_actions"
+ android:layout_width="fill_parent"
+ android:layout_height="83dip"
+ android:layout_alignParentBottom="true"
+
+ android:inAnimation="@anim/fade_in_fast"
+ android:outAnimation="@anim/fade_out_fast"
+
+ android:foregroundGravity="top|fill_horizontal"
+ android:foreground="@*android:drawable/title_bar_shadow"
+ android:background="@android:drawable/title_bar_tall">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_horizontal"
+
+ android:gravity="center_vertical"
+
+ android:shadowColor="#FF000000"
+ android:shadowRadius="2.0"
+
+ android:drawablePadding="6dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/gestures_instructions" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_horizontal"
+
+ android:gravity="center_vertical"
+
+ android:shadowColor="#FF000000"
+ android:shadowRadius="2.0"
+
+ android:drawablePadding="6dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/gestures_instructions" />
+
+ </ViewSwitcher>
+
+ <android.gesture.GestureOverlayView
+ android:id="@+id/gestures_overlay"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:layout_alignParentTop="true"
+ android:layout_above="@id/gestures_actions"
+
+ android:background="@drawable/gestures_background"
+
+ android:gestureStrokeType="multiple" />
+
+ <ImageButton
+ style="@style/PlusButton"
+
+ android:id="@+id/gestures_add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignTop="@id/gestures_actions"
+ android:layout_marginRight="10dip"
+ android:layout_marginTop="-22dip" />
+
+</com.android.launcher.GesturesPanel>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+
+ <TextView
+ android:id="@android:id/empty"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+
+ android:gravity="center_horizontal"
+
+ android:text="@string/gestures_loading"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</FrameLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+
+ android:drawablePadding="12dip"
+ android:paddingLeft="6dip"
+ android:paddingRight="2dip"
+
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
android:orientation="vertical">
<TextView
+ android:id="@+id/label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/rename_folder_label"
<color name="bubble_dark_background">#B2191919</color>
<color name="delete_color_filter">#A5FF0000</color>
- <color name="appwidget_error_color">#fccc</color>
- <color name="snag_callout_color">#f444</color>
+ <color name="appwidget_error_color">#FCCC</color>
+ <color name="snag_callout_color">#F444</color>
+
+ <color name="gesture_color">#FFFFFF00</color>
</resources>
<resources>
<dimen name="search_widget_inset">19dip</dimen>
+ <dimen name="gesture_thumbnail_inset">8dip</dimen>
+ <dimen name="gesture_thumbnail_size">64dip</dimen>
</resources>
<string name="menu_search">Search</string>
<!-- Noun, menu item used to bring down the notifications shade -->
<string name="menu_notifications">Notifications</string>
- <!-- Noun, menu item used to show the system settings -->
+ <!-- Noun, menu item used to show the gestures settings -->
+ <string name="menu_gestures">Gestures</string>
+ <!-- Noun, menu item used to show the system settings -->
<string name="menu_settings">Settings</string>
<!-- Permissions: -->
<!-- Text to show user in place of a gadget when we can't display it properly -->
<string name="gadget_error_text">Problem loading widget</string>
+ <!-- Gestures: -->
+ <skip />
+
+ <!-- Message displayed when the user enters gestures mode and is asked to draw a gesture -->
+ <string name="gestures_instructions">Draw a gesture to get started</string>
+ <!-- Message displayed when the gesture entered by the user cannot be recognized -->
+ <string name="gestures_unknown">Unknown gesture</string>
+ <!-- Message displayed when the user has successfully created a new gesture -->
+ <string name="gestures_created">Added gesture "%s"</string>
+ <!-- Message displayed when the user could not create a new gesture -->
+ <string name="gestures_failed">Gesture could not be created</string>
+ <!-- Message displayed when the user opens the gestures settings screen -->
+ <string name="gestures_loading">Loading gestures...</string>
+ <!-- Message displayed when the user has no gestures -->
+ <string name="gestures_empty">No gestures defined</string>
+ <!-- Title of the screen used to view/manage gestures -->
+ <string name="gestures_activity">Gestures</string>
+ <!-- Noun, menu item used to rename a gesture -->
+ <string name="gestures_rename">Rename</string>
+ <!-- Noun, menu item used to remove a gesture -->
+ <string name="gestures_delete">Delete</string>
+ <!-- Message displayed when a gesture is successfully deleted -->
+ <string name="gestures_delete_success">Gesture deleted</string>
+ <!-- Title of dialog box -->
+ <string name="gestures_rename_title">Rename gesture</string>
+ <!-- Label of gesture name field in Rename gesture dialog box -->
+ <string name="gestures_rename_label">Gesture name</string>
</resources>
<item name="android:paddingLeft">10dip</item>
<item name="android:paddingRight">10dip</item>
</style>
+
+ <style name="PlusButton">
+ <item name="android:background">@drawable/btn_circle</item>
+ <item name="android:src">@drawable/ic_btn_round_plus</item>
+ </style>
+
</resources>
Intent.ShortcutIconResource iconResource;
ApplicationInfo() {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
public ApplicationInfo(ApplicationInfo info) {
/**
* Creates the application intent based on a component name and various launch flags.
- * Sets {@link #itemType} to {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}.
+ * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
*
* @param className the class name of the component representing the intent
* @param launchFlags the launch flags
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(className);
intent.setFlags(launchFlags);
- itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}
@Override
super.onAddToDatabase(values);
String titleStr = title != null ? title.toString() : null;
- values.put(LauncherSettings.Favorites.TITLE, titleStr);
+ values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
String uri = intent != null ? intent.toURI() : null;
- values.put(LauncherSettings.Favorites.INTENT, uri);
+ values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
if (customIcon) {
- values.put(LauncherSettings.Favorites.ICON_TYPE,
- LauncherSettings.Favorites.ICON_TYPE_BITMAP);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
Bitmap bitmap = ((FastBitmapDrawable) icon).getBitmap();
writeBitmap(values, bitmap);
} else {
- values.put(LauncherSettings.Favorites.ICON_TYPE,
- LauncherSettings.Favorites.ICON_TYPE_RESOURCE);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
if (iconResource != null) {
- values.put(LauncherSettings.Favorites.ICON_PACKAGE, iconResource.packageName);
- values.put(LauncherSettings.Favorites.ICON_RESOURCE, iconResource.resourceName);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
+ iconResource.packageName);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+ iconResource.resourceName);
}
}
}
--- /dev/null
+/*
+ * Copyright (C) 2009 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.launcher;
+
+import android.app.ListActivity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.os.AsyncTask;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.EditText;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.text.TextUtils;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Map;
+
+public class GesturesActivity extends ListActivity {
+ private static final int MENU_ID_RENAME = 1;
+ private static final int MENU_ID_REMOVE = 2;
+
+ private static final int DIALOG_RENAME_GESTURE = 1;
+
+ private final Comparator<ApplicationInfo> mSorter =
+ new LauncherModel.ApplicationInfoComparator();
+
+ private GesturesAdapter mAdapter;
+ private GestureLibrary mStore;
+ private GesturesLoadTask mTask;
+ private TextView mEmpty;
+
+ private Dialog mRenameDialog;
+ private EditText mInput;
+ private ApplicationInfo mCurrentRenameInfo;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.gestures_settings);
+
+ mAdapter = new GesturesAdapter(this);
+ setListAdapter(mAdapter);
+
+ mStore = Launcher.getGestureLibrary();
+ mEmpty = (TextView) findViewById(android.R.id.empty);
+ mTask = (GesturesLoadTask) new GesturesLoadTask().execute();
+
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) {
+ mTask.cancel(true);
+ mTask = null;
+ }
+
+ cleanupRenameDialog();
+ }
+
+ private void checkForEmpty() {
+ if (mAdapter.getCount() == 0) {
+ mEmpty.setText(R.string.gestures_empty);
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenu.ContextMenuInfo menuInfo) {
+
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ menu.setHeaderTitle(((TextView) info.targetView).getText());
+
+ menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename);
+ menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)
+ item.getMenuInfo();
+ final ApplicationInfo info = (ApplicationInfo) menuInfo.targetView.getTag();
+
+ switch (item.getItemId()) {
+ case MENU_ID_RENAME:
+ renameGesture(info);
+ return true;
+ case MENU_ID_REMOVE:
+ deleteGesture(info);
+ return true;
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ private void renameGesture(ApplicationInfo info) {
+ mCurrentRenameInfo = info;
+ showDialog(DIALOG_RENAME_GESTURE);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == DIALOG_RENAME_GESTURE) {
+ return createRenameDialog();
+ }
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ if (id == DIALOG_RENAME_GESTURE) {
+ mInput.setText(mCurrentRenameInfo.title);
+ }
+ }
+
+ private Dialog createRenameDialog() {
+ final View layout = View.inflate(this, R.layout.rename_folder, null);
+ mInput = (EditText) layout.findViewById(R.id.folder_name);
+ ((TextView) layout.findViewById(R.id.label)).setText(R.string.gestures_rename_label);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setIcon(0);
+ builder.setTitle(getString(R.string.gestures_rename_title));
+ builder.setCancelable(true);
+ builder.setOnCancelListener(new Dialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cleanupRenameDialog();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ cleanupRenameDialog();
+ }
+ }
+ );
+ builder.setPositiveButton(getString(R.string.rename_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ changeGestureName();
+ }
+ }
+ );
+ builder.setView(layout);
+ return builder.create();
+ }
+
+ private void changeGestureName() {
+ final String name = mInput.getText().toString();
+ if (!TextUtils.isEmpty(name)) {
+ mCurrentRenameInfo.title = mInput.getText();
+ LauncherModel.updateGestureInDatabase(this, mCurrentRenameInfo);
+ }
+ }
+
+ private void cleanupRenameDialog() {
+ if (mRenameDialog != null) {
+ mRenameDialog.dismiss();
+ mRenameDialog = null;
+ mInput = null;
+ }
+ }
+
+ private void deleteGesture(ApplicationInfo info) {
+ mStore.removeEntry(String.valueOf(info.id));
+ // TODO: On a thread?
+ mStore.save();
+
+ final GesturesActivity.GesturesAdapter adapter = mAdapter;
+ adapter.setNotifyOnChange(false);
+ adapter.remove(info);
+ adapter.sort(mSorter);
+ checkForEmpty();
+ adapter.notifyDataSetChanged();
+
+ LauncherModel.deleteGestureFromDatabase(this, info);
+
+ Toast.makeText(this, R.string.gestures_delete_success, Toast.LENGTH_SHORT).show();
+ }
+
+ private class GesturesLoadTask extends AsyncTask<Void, ApplicationInfo, Boolean> {
+ private int mThumbnailSize;
+ private int mThumbnailInset;
+ private int mPathColor;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ final Resources resources = getResources();
+ mPathColor = resources.getColor(R.color.gesture_color);
+ mThumbnailInset = (int) resources.getDimension(R.dimen.gesture_thumbnail_inset);
+ mThumbnailSize = (int) resources.getDimension(R.dimen.gesture_thumbnail_size);
+ }
+
+ protected Boolean doInBackground(Void... params) {
+ if (isCancelled()) return Boolean.FALSE;
+
+ final GestureLibrary store = mStore;
+
+ if (store.load()) {
+ final LauncherModel model = Launcher.getModel();
+
+ for (String name : store.getGestureEntries()) {
+ final Gesture gesture = store.getGestures(name).get(0);
+ final Bitmap bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
+ mThumbnailInset, mPathColor);
+ final ApplicationInfo info = model.queryGesture(GesturesActivity.this, name);
+
+ mAdapter.addBitmap(info.id, bitmap);
+ publishProgress(info);
+ }
+
+ return Boolean.TRUE;
+ }
+
+ return Boolean.FALSE;
+ }
+
+ @Override
+ protected void onProgressUpdate(ApplicationInfo... values) {
+ super.onProgressUpdate(values);
+
+ final GesturesActivity.GesturesAdapter adapter = mAdapter;
+ adapter.setNotifyOnChange(false);
+
+ for (ApplicationInfo info : values) {
+ adapter.add(info);
+ }
+
+ adapter.sort(mSorter);
+ adapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean aBoolean) {
+ super.onPostExecute(aBoolean);
+ checkForEmpty();
+ }
+ }
+
+ private class GesturesAdapter extends ArrayAdapter<ApplicationInfo> {
+ private final LayoutInflater mInflater;
+ private final Map<Long, Drawable> mThumbnails = Collections.synchronizedMap(
+ new HashMap<Long, Drawable>());
+
+ public GesturesAdapter(Context context) {
+ super(context, 0);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ void addBitmap(Long id, Bitmap bitmap) {
+ mThumbnails.put(id, new BitmapDrawable(bitmap));
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.gestures_settings_item, parent, false);
+ }
+
+ final ApplicationInfo info = getItem(position);
+ final TextView label = (TextView) convertView;
+
+ label.setTag(info);
+ label.setText(info.title);
+ label.setCompoundDrawablesWithIntrinsicBounds(info.icon, null,
+ mThumbnails.get(info.id), null);
+
+ return convertView;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2009 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.launcher;
+
+interface GesturesConstants {
+ final double PREDICTION_THRESHOLD = 1.0;
+ final String STORE_NAME = "gestures";
+ final long MATCH_DELAY = 370;
+ final float LENGTH_THRESHOLD = 120.0f;
+ int PATH_SAMPLE_COUNT = 10;
+}
--- /dev/null
+/*
+ * Copyright (C) 2009 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.launcher;
+
+import android.widget.RelativeLayout;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+public class GesturesPanel extends RelativeLayout {
+ public GesturesPanel(Context context) {
+ super(context);
+ }
+
+ public GesturesPanel(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ ((Launcher) mContext).hideGesturesPanel();
+ return true;
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+}
*/
int spanY = 1;
+ /**
+ * Indicates whether the item is a gesture.
+ */
+ boolean isGesture = false;
+
ItemInfo() {
}
* @param values
*/
void onAddToDatabase(ContentValues values) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
- values.put(LauncherSettings.Favorites.CELLX, cellX);
- values.put(LauncherSettings.Favorites.CELLY, cellY);
- values.put(LauncherSettings.Favorites.SPANX, spanX);
- values.put(LauncherSettings.Favorites.SPANY, spanY);
+ values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+ if (!isGesture) {
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ values.put(LauncherSettings.Favorites.SPANX, spanX);
+ values.put(LauncherSettings.Favorites.SPANY, spanY);
+ }
}
static void writeBitmap(ContentValues values, Bitmap bitmap) {
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
-import android.util.Log;
import static android.util.Log.*;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.MotionEvent;
+import android.view.Gravity;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.SlidingDrawer;
import android.widget.TextView;
import android.widget.Toast;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.ViewSwitcher;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.gesture.GestureOverlayView;
+import android.gesture.GestureLibraries;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.gesture.Prediction;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
private static final boolean PROFILE_DRAWER = false;
private static final boolean PROFILE_ROTATE = false;
private static final boolean DEBUG_USER_INTERFACE = false;
+ private static final boolean DEBUG_GESTURES = false;
private static final int WALLPAPER_SCREENS_SPAN = 2;
private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
- private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
+ private static final int MENU_GESTURES = MENU_NOTIFICATIONS + 1;
+ private static final int MENU_SETTINGS = MENU_GESTURES + 1;
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
private static final int REQUEST_PICK_SHORTCUT = 7;
private static final int REQUEST_PICK_LIVE_FOLDER = 8;
private static final int REQUEST_PICK_APPWIDGET = 9;
+ private static final int REQUEST_PICK_GESTURE_ACTION = 10;
+ private static final int REQUEST_CREATE_GESTURE_ACTION = 11;
+ private static final int REQUEST_CREATE_GESTURE_APPLICATION_ACTION = 12;
static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
// Type: long
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+ // Type: Gesture (Parcelable)
+ private static final String RUNTIME_STATE_PENDING_GESTURE = "launcher.gesture";
private static final LauncherModel sModel = new LauncherModel();
private static WallpaperIntentReceiver sWallpaperReceiver;
+ private static GestureLibrary sLibrary;
+
private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
private final ContentObserver mObserver = new FavoritesChangeObserver();
private DesktopBinder mBinder;
+ private View mGesturesPanel;
+ private GestureOverlayView mGesturesOverlay;
+ private ViewSwitcher mGesturesPrompt;
+ private ImageView mGesturesAdd;
+ private PopupWindow mGesturesWindow;
+ private Launcher.GesturesProcessor mGesturesProcessor;
+ private Gesture mCurrentGesture;
+ private GesturesAction mGesturesAction;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInflater = getLayoutInflater();
+ if (sLibrary == null) {
+ // The context is not kept by the library so it's safe to do this
+ sLibrary = GestureLibraries.fromPrivateFile(Launcher.this,
+ GesturesConstants.STORE_NAME);
+ }
+
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
// For example, the user would PICK_SHORTCUT for "Music playlist", and we
// launch over to the Music app to actually CREATE_SHORTCUT.
- if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
+ if (resultCode == RESULT_OK && (mAddItemCellInfo != null ||
+ ((requestCode == REQUEST_PICK_GESTURE_ACTION ||
+ requestCode == REQUEST_CREATE_GESTURE_ACTION ||
+ requestCode == REQUEST_CREATE_GESTURE_APPLICATION_ACTION) && mCurrentGesture != null))) {
+
switch (requestCode) {
case REQUEST_PICK_APPLICATION:
completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked);
break;
case REQUEST_PICK_SHORTCUT:
- addShortcut(data);
+ processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT);
break;
case REQUEST_CREATE_SHORTCUT:
completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked);
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked);
break;
+ case REQUEST_PICK_GESTURE_ACTION:
+ processShortcut(data, REQUEST_CREATE_GESTURE_APPLICATION_ACTION,
+ REQUEST_CREATE_GESTURE_ACTION);
+ break;
+ case REQUEST_CREATE_GESTURE_ACTION:
+ completeCreateGesture(data, true);
+ break;
+ case REQUEST_CREATE_GESTURE_APPLICATION_ACTION:
+ completeCreateGesture(data, false);
+ break;
}
} else if (requestCode == REQUEST_PICK_APPWIDGET &&
resultCode == RESULT_CANCELED && data != null) {
@Override
protected void onPause() {
super.onPause();
+ if (mGesturesWindow != null) {
+ mGesturesWindow.setAnimationStyle(0);
+ mGesturesWindow.update();
+ }
closeDrawer(false);
}
@Override
+ protected void onStop() {
+ super.onStop();
+ hideGesturesPanel();
+ }
+
+ @Override
public Object onRetainNonConfigurationInstance() {
// Flag any binder to stop early before switching
if (mBinder != null) {
mFolderInfo = sModel.getFolderById(this, id);
mRestoring = true;
}
+
+ mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_PENDING_GESTURE);
}
/**
dragLayer.setIgnoredDropTarget(grid);
dragLayer.setDragScoller(workspace);
dragLayer.setDragListener(deleteZone);
+
+ mGesturesPanel = mInflater.inflate(R.layout.gestures, mDragLayer, false);
+ final View gesturesPanel = mGesturesPanel;
+
+ mGesturesPrompt = (ViewSwitcher) gesturesPanel.findViewById(R.id.gestures_actions);
+ mGesturesAction = new GesturesAction();
+
+ mGesturesPrompt.getChildAt(0).setOnClickListener(mGesturesAction);
+ mGesturesPrompt.getChildAt(1).setOnClickListener(mGesturesAction);
+
+ mGesturesAdd = (ImageView) gesturesPanel.findViewById(R.id.gestures_add);
+ final ImageView gesturesAdd = mGesturesAdd;
+ gesturesAdd.setAlpha(128);
+ gesturesAdd.setEnabled(false);
+ gesturesAdd.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ createGesture();
+ }
+ });
+
+ mGesturesOverlay = (GestureOverlayView) gesturesPanel.findViewById(R.id.gestures_overlay);
+ mGesturesProcessor = new GesturesProcessor();
+
+ final GestureOverlayView overlay = mGesturesOverlay;
+ overlay.setFadeOffset(GesturesConstants.MATCH_DELAY);
+ overlay.addOnGestureListener(mGesturesProcessor);
+ overlay.getGesturePaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+ }
+
+ private void createGesture() {
+ mCurrentGesture = mGesturesOverlay.getGesture();
+ mWaitingForResult = true;
+ pickShortcut(REQUEST_PICK_GESTURE_ACTION, R.string.title_select_shortcut);
+ }
+
+ private void completeCreateGesture(Intent data, boolean isShortcut) {
+ ApplicationInfo info;
+
+ if (isShortcut) {
+ info = infoFromShortcutIntent(this, data);
+ } else {
+ info = infoFromApplicationIntent(this, data);
+ }
+
+ boolean success = false;
+ if (info != null) {
+ info.isGesture = true;
+
+ if (LauncherModel.addGestureToDatabase(this, info, false)) {
+ mGesturesProcessor.addGesture(String.valueOf(info.id), mCurrentGesture);
+ mGesturesProcessor.update(info, mCurrentGesture);
+ Toast.makeText(this, getString(R.string.gestures_created, info.title),
+ Toast.LENGTH_SHORT).show();
+ success = true;
+ }
+ }
+
+ if (!success) {
+ Toast.makeText(this, getString(R.string.gestures_failed), Toast.LENGTH_SHORT).show();
+ }
+
+ mCurrentGesture = null;
}
/**
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
- // Find details for this application
+ final ApplicationInfo info = infoFromApplicationIntent(context, data);
+ if (info != null) {
+ mWorkspace.addApplicationShortcut(info, cellInfo, insertAtFirst);
+ }
+ }
+
+ private static ApplicationInfo infoFromApplicationIntent(Context context, Intent data) {
ComponentName component = data.getComponent();
PackageManager packageManager = context.getPackageManager();
ActivityInfo activityInfo = null;
try {
activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */);
} catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
+ e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
}
if (activityInfo != null) {
itemInfo.icon = activityInfo.loadIcon(packageManager);
itemInfo.container = ItemInfo.NO_ID;
- mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst);
+ return itemInfo;
}
+
+ return null;
}
/**
static ApplicationInfo addShortcut(Context context, Intent data,
CellLayout.CellInfo cellInfo, boolean notify) {
+ final ApplicationInfo info = infoFromShortcutIntent(context, data);
+ LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
+
+ return info;
+ }
+
+ private static ApplicationInfo infoFromShortcutIntent(Context context, Intent data) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
Drawable icon = null;
boolean filtered = false;
boolean customIcon = false;
- Intent.ShortcutIconResource iconResource = null;
+ ShortcutIconResource iconResource = null;
if (bitmap != null) {
icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
customIcon = true;
} else {
Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
- if (extra != null && extra instanceof Intent.ShortcutIconResource) {
+ if (extra != null && extra instanceof ShortcutIconResource) {
try {
- iconResource = (Intent.ShortcutIconResource) extra;
+ iconResource = (ShortcutIconResource) extra;
final PackageManager packageManager = context.getPackageManager();
Resources resources = packageManager.getResourcesForApplication(
iconResource.packageName);
info.customIcon = customIcon;
info.iconResource = iconResource;
- LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
return info;
}
// An exception is thrown if the dialog is not visible, which is fine
}
- // If we are already in front we go back to the default screen,
- // otherwise we don't
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) !=
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
- if (!mWorkspace.isDefaultScreenShowing()) {
- mWorkspace.moveToDefaultScreen();
+
+ if (mGesturesPanel != null && mDragLayer.getWindowVisibility() == View.VISIBLE) {
+ onHomeKeyPressed();
}
closeDrawer();
- View v = getWindow().peekDecorView();
+
+ final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(
INPUT_METHOD_SERVICE);
}
}
+ private void onHomeKeyPressed() {
+ if (mGesturesWindow == null || !mGesturesWindow.isShowing()) {
+ showGesturesPanel();
+ } else {
+ hideGesturesPanel();
+ }
+ }
+
+ private void showGesturesPanel() {
+ resetGesturesPrompt();
+
+ mGesturesAdd.setEnabled(false);
+ mGesturesAdd.setAlpha(128);
+
+ mGesturesOverlay.clear(false);
+
+ PopupWindow window;
+ if (mGesturesWindow == null) {
+ mGesturesWindow = new PopupWindow(this);
+ window = mGesturesWindow;
+ window.setFocusable(true);
+ window.setTouchable(true);
+ window.setBackgroundDrawable(null);
+ window.setContentView(mGesturesPanel);
+ } else {
+ window = mGesturesWindow;
+ }
+ window.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard);
+
+ final int[] xy = new int[2];
+ final DragLayer dragLayer = mDragLayer;
+ dragLayer.getLocationOnScreen(xy);
+
+ window.setWidth(dragLayer.getWidth());
+ window.setHeight(dragLayer.getHeight() - 1);
+ window.showAtLocation(dragLayer, Gravity.TOP | Gravity.LEFT, xy[0], xy[1] + 1);
+ }
+
+ private void resetGesturesPrompt() {
+ mGesturesAction.intent = null;
+ final TextView prompt = (TextView) mGesturesPrompt.getCurrentView();
+ prompt.setText(R.string.gestures_instructions);
+ prompt.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ prompt.setClickable(false);
+ }
+
+ private void resetGesturesNextPrompt() {
+ mGesturesAction.intent = null;
+ setGesturesNextPrompt(null, getString(R.string.gestures_instructions));
+ mGesturesPrompt.getNextView().setClickable(false);
+ }
+
+ private void setGesturesNextPrompt(Drawable icon, CharSequence title) {
+ final TextView prompt = (TextView) mGesturesPrompt.getNextView();
+ prompt.setText(title);
+ prompt.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ prompt.setClickable(true);
+ mGesturesPrompt.showNext();
+ }
+
+ void hideGesturesPanel() {
+ if (mGesturesWindow != null) {
+ mGesturesWindow.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard);
+ mGesturesWindow.update();
+ mGesturesWindow.dismiss();
+ }
+ }
+
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Do not call super here
outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
}
+
+ if (mCurrentGesture != null && mWaitingForResult) {
+ outState.putParcelable(RUNTIME_STATE_PENDING_GESTURE, mCurrentGesture);
+ }
}
@Override
.setIcon(com.android.internal.R.drawable.ic_menu_notifications)
.setAlphabeticShortcut('N');
+ final Intent gestures = new Intent(this, GesturesActivity.class);
+ menu.add(0, MENU_GESTURES, 0, R.string.menu_gestures)
+ .setIcon(com.android.internal.R.drawable.ic_menu_compose).setAlphabeticShortcut('G')
+ .setIntent(gestures);
+
final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
}
- void addShortcut(Intent intent) {
+ void processShortcut(Intent intent, int requestCodeApplication, int requestCodeShortcut) {
// Handle case where user selected "Applications"
String applicationName = getResources().getString(R.string.group_applications);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
- startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION);
+ startActivityForResult(pickIntent, requestCodeApplication);
} else {
- startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
+ startActivityForResult(intent, requestCodeShortcut);
}
}
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
+ e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
". Make sure to create a MAIN intent-filter for the corresponding activity " +
"or use the exported attribute for this activity.", e);
}
return sModel;
}
+ static GestureLibrary getGestureLibrary() {
+ return sLibrary;
+ }
+
void closeAllApplications() {
mDrawer.close();
}
showDialog(DIALOG_CREATE_SHORTCUT);
}
+ private void pickShortcut(int requestCode, int title) {
+ Bundle bundle = new Bundle();
+
+ ArrayList<String> shortcutNames = new ArrayList<String>();
+ shortcutNames.add(getString(R.string.group_applications));
+ bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
+
+ ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
+ shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
+ R.drawable.ic_launcher_application));
+ bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
+
+ Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+ pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
+ pickIntent.putExtra(Intent.EXTRA_TITLE, getText(title));
+ pickIntent.putExtras(bundle);
+
+ startActivityForResult(pickIntent, requestCode);
+ }
+
private class RenameFolder {
private EditText mInput;
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
// Insert extra item to handle picking application
- Bundle bundle = new Bundle();
-
- ArrayList<String> shortcutNames = new ArrayList<String>();
- shortcutNames.add(res.getString(R.string.group_applications));
- bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
-
- ArrayList<ShortcutIconResource> shortcutIcons =
- new ArrayList<ShortcutIconResource>();
- shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
- R.drawable.ic_launcher_application));
- bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
-
- Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
- pickIntent.putExtra(Intent.EXTRA_INTENT,
- new Intent(Intent.ACTION_CREATE_SHORTCUT));
- pickIntent.putExtra(Intent.EXTRA_TITLE,
- getText(R.string.title_select_shortcut));
- pickIntent.putExtras(bundle);
-
- startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
+ pickShortcut(REQUEST_PICK_SHORTCUT, R.string.title_select_shortcut);
break;
}
}
}
}
+
+ private class GesturesProcessor implements GestureOverlayView.OnGestureListener,
+ GestureOverlayView.OnGesturePerformedListener {
+
+ private final GestureMatcher mMatcher = new GestureMatcher();
+
+ GesturesProcessor() {
+ // TODO: Maybe the load should happen on a background thread?
+ sLibrary.load();
+ }
+
+ public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
+ overlay.removeCallbacks(mMatcher);
+ resetGesturesNextPrompt();
+
+ mGesturesAdd.setAlpha(128);
+ mGesturesAdd.setEnabled(false);
+ }
+
+ public void onGesture(GestureOverlayView overlay, MotionEvent event) {
+ }
+
+ public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
+ }
+
+ public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
+ overlay.removeCallbacks(mMatcher);
+
+ mMatcher.gesture = overlay.getGesture();
+ if (mMatcher.gesture.getLength() < GesturesConstants.LENGTH_THRESHOLD) {
+ overlay.clear(false);
+ } else {
+ overlay.postDelayed(mMatcher, GesturesConstants.MATCH_DELAY);
+ }
+ }
+
+ private void matchGesture(Gesture gesture) {
+ mGesturesAdd.setAlpha(255);
+ mGesturesAdd.setEnabled(true);
+
+ if (gesture != null) {
+ final ArrayList<Prediction> predictions = sLibrary.recognize(gesture);
+
+ if (DEBUG_GESTURES) {
+ for (Prediction p : predictions) {
+ d(LOG_TAG, String.format("name=%s, score=%f", p.name, p.score));
+ }
+ }
+
+ boolean match = false;
+ if (predictions.size() > 0) {
+ final Prediction prediction = predictions.get(0);
+ if (prediction.score > GesturesConstants.PREDICTION_THRESHOLD) {
+ match = true;
+
+ ApplicationInfo info = sModel.queryGesture(Launcher.this, prediction.name);
+ if (info != null) {
+ updatePrompt(info);
+ }
+ }
+ }
+
+ if (!match){
+ setGesturesNextPrompt(null, getString(R.string.gestures_unknown));
+ }
+ }
+ }
+
+ private void updatePrompt(ApplicationInfo info) {
+ setGesturesNextPrompt(info.icon, info.title);
+ mGesturesAction.intent = info.intent;
+ }
+
+ public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
+ overlay.removeCallbacks(mMatcher);
+ }
+
+ void addGesture(String name, Gesture gesture) {
+ sLibrary.addGesture(name, gesture);
+ // TODO: On a background thread?
+ sLibrary.save();
+ }
+
+ void update(ApplicationInfo info, Gesture gesture) {
+ mGesturesOverlay.setGesture(gesture);
+ updatePrompt(info);
+ }
+
+ class GestureMatcher implements Runnable {
+ Gesture gesture;
+
+ public void run() {
+ if (gesture != null) {
+ matchGesture(gesture);
+ }
+ }
+ }
+ }
+
+ private class GesturesAction implements View.OnClickListener {
+ Intent intent;
+
+ public void onClick(View v) {
+ if (intent != null) {
+ startActivitySafely(intent);
+ }
+ }
+ }
}
}
}
- private static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
+ static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
public final int compare(ApplicationInfo a, ApplicationInfo b) {
return sCollator.compare(a.title.toString(), b.title.toString());
}
private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.TITLE,
+ new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
null, null, null);
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
try {
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
/**
* Make an ApplicationInfo object for a sortcut
*/
- private ApplicationInfo getApplicationInfoShortcut(Cursor c, Launcher launcher,
+ private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
final ApplicationInfo info = new ApplicationInfo();
case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
String packageName = c.getString(iconPackageIndex);
String resourceName = c.getString(iconResourceIndex);
- PackageManager packageManager = launcher.getPackageManager();
+ PackageManager packageManager = context.getPackageManager();
try {
Resources resources = packageManager.getResourcesForApplication(packageName);
final int id = resources.getIdentifier(resourceName, null, null);
- info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), launcher);
+ info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
} catch (Exception e) {
info.icon = packageManager.getDefaultActivityIcon();
}
try {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
info.icon = new FastBitmapDrawable(
- Utilities.createBitmapThumbnail(bitmap, launcher));
+ Utilities.createBitmapThumbnail(bitmap, context));
} catch (Exception e) {
- packageManager = launcher.getPackageManager();
+ packageManager = context.getPackageManager();
info.icon = packageManager.getDefaultActivityIcon();
}
info.filtered = true;
info.customIcon = true;
break;
default:
- info.icon = launcher.getPackageManager().getDefaultActivityIcon();
+ info.icon = context.getPackageManager().getDefaultActivityIcon();
info.customIcon = false;
break;
}
}
/**
+ * Add an item to the database in a specified container. Sets the container, screen, cellX and
+ * cellY fields of the item. Also assigns an ID to the item.
+ */
+ static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) {
+ final ContentValues values = new ContentValues();
+ final ContentResolver cr = context.getContentResolver();
+
+ item.onAddToDatabase(values);
+
+ Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI :
+ LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values);
+
+ if (result != null) {
+ item.id = Integer.parseInt(result.getPathSegments().get(1));
+ }
+
+ return result != null;
+ }
+
+ /**
* Update an item to the database in a specified container.
*/
static void updateItemInDatabase(Context context, ItemInfo item) {
cr.delete(LauncherSettings.Favorites.CONTENT_URI,
LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
}
+
+ static void deleteGestureFromDatabase(Context context, ItemInfo item) {
+ final ContentResolver cr = context.getContentResolver();
+
+ cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null);
+ }
+
+ static void updateGestureInDatabase(Context context, ItemInfo item) {
+ final ContentValues values = new ContentValues();
+ final ContentResolver cr = context.getContentResolver();
+
+ item.onAddToDatabase(values);
+
+ cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null);
+ }
+
+
+ ApplicationInfo queryGesture(Context context, String id) {
+ final ContentResolver contentResolver = context.getContentResolver();
+ final PackageManager manager = context.getPackageManager();
+ final Cursor c = contentResolver.query(
+ LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?",
+ new String[] { id }, null);
+
+ ApplicationInfo info = null;
+
+ try {
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID);
+ final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT);
+ final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE);
+ final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE);
+ final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON);
+ final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE);
+ final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE);
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE);
+
+ String intentDescription;
+ Intent intent;
+
+ if (c.moveToNext()) {
+ int itemType = c.getInt(itemTypeIndex);
+
+ switch (itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ intentDescription = c.getString(intentIndex);
+ try {
+ intent = Intent.getIntent(intentDescription);
+ } catch (java.net.URISyntaxException e) {
+ return null;
+ }
+
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ info = getApplicationInfo(manager, intent, context);
+ } else {
+ info = getApplicationInfoShortcut(c, context, iconTypeIndex,
+ iconPackageIndex, iconResourceIndex, iconIndex);
+ }
+
+ if (info == null) {
+ info = new ApplicationInfo();
+ info.icon = manager.getDefaultActivityIcon();
+ }
+
+ info.isGesture = true;
+ info.title = c.getString(titleIndex);
+ info.intent = intent;
+ info.id = c.getLong(idIndex);
+
+ break;
+ }
+ }
+ } catch (Exception e) {
+ w(LOG_TAG, "Could not load gesture with name " + id);
+ } finally {
+ c.close();
+ }
+
+ return info;
+ }
}
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 3;
+ private static final int DATABASE_VERSION = 4;
static final String AUTHORITY = "com.android.launcher.settings";
static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets";
static final String TABLE_FAVORITES = "favorites";
+ static final String TABLE_GESTURES = "gestures";
static final String PARAMETER_NOTIFY = "notify";
/**
- * {@link Uri} triggered at any registered {@link ContentObserver} when
+ * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
* {@link AppWidgetHost#deleteHost()} is called during database creation.
* Use this to recall {@link AppWidgetHost#startListening()} if needed.
*/
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
"displayMode INTEGER" +
");");
+ db.execSQL("CREATE TABLE gestures (" +
+ "_id INTEGER PRIMARY KEY," +
+ "title TEXT," +
+ "intent TEXT," +
+ "itemType INTEGER," +
+ "iconType INTEGER," +
+ "iconPackage TEXT," +
+ "iconResource TEXT," +
+ "icon BLOB" +
+ ");");
+
// Database was just created, so wipe any previous widgets
if (mAppWidgetHost != null) {
mAppWidgetHost.deleteHost();
}
private int copyFromCursor(SQLiteDatabase db, Cursor c) {
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
int i = 0;
while (c.moveToNext()) {
ContentValues values = new ContentValues(c.getColumnCount());
- values.put(LauncherSettings.Favorites.ID, c.getLong(idIndex));
+ values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
convertWidgets(db);
}
}
+
+ if (version < 4) {
+ db.beginTransaction();
+ try {
+ db.execSQL("CREATE TABLE gestures (" +
+ "_id INTEGER PRIMARY KEY," +
+ "title TEXT," +
+ "intent TEXT," +
+ "itemType INTEGER," +
+ "iconType INTEGER," +
+ "iconPackage TEXT," +
+ "iconResource TEXT," +
+ "icon BLOB" +
+ ");");
+ db.setTransactionSuccessful();
+ version = 4;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(LOG_TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
if (version != DATABASE_VERSION) {
Log.w(LOG_TAG, "Destroying all old data.");
* Settings related utilities.
*/
class LauncherSettings {
- /**
- * Favorites. When changing these values, be sure to update
- * {@link com.android.settings.LauncherAppWidgetBinder} as needed.
- */
- static final class Favorites implements BaseColumns {
+ static interface BaseLauncherColumns extends BaseColumns {
+ /**
+ * Descriptive name of the gesture that can be displayed to the user.
+ * <P>Type: TEXT</P>
+ */
+ static final String TITLE = "title";
+
/**
+ * The Intent URL of the gesture, describing what it points to. This
+ * value is given to {@link android.content.Intent#getIntent} to create
+ * an Intent that can be launched.
+ * <P>Type: TEXT</P>
+ */
+ static final String INTENT = "intent";
+
+ /**
+ * The type of the gesture
+ *
+ * <P>Type: INTEGER</P>
+ */
+ static final String ITEM_TYPE = "itemType";
+
+ /**
+ * The gesture is an application
+ */
+ static final int ITEM_TYPE_APPLICATION = 0;
+
+ /**
+ * The gesture is an application created shortcut
+ */
+ static final int ITEM_TYPE_SHORTCUT = 1;
+
+ /**
+ * The icon type.
+ * <P>Type: INTEGER</P>
+ */
+ static final String ICON_TYPE = "iconType";
+
+ /**
+ * The icon is a resource identified by a package name and an integer id.
+ */
+ static final int ICON_TYPE_RESOURCE = 0;
+
+ /**
+ * The icon is a bitmap.
+ */
+ static final int ICON_TYPE_BITMAP = 1;
+
+ /**
+ * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_PACKAGE = "iconPackage";
+
+ /**
+ * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_RESOURCE = "iconResource";
+
+ /**
+ * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+ * <P>Type: BLOB</P>
+ */
+ static final String ICON = "icon";
+ }
+
+ static final class Gestures implements BaseLauncherColumns {
+ /**
* The content:// style URL for this table
*/
static final Uri CONTENT_URI = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
"?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
/**
* sent if the content changes.
*/
static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
"?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
/**
*/
static Uri getContentUri(long id, boolean notify) {
return Uri.parse("content://" + LauncherProvider.AUTHORITY +
- "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" +
+ "/" + LauncherProvider.TABLE_GESTURES + "/" + id + "?" +
LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
}
+ }
+ /**
+ * Favorites. When changing these values, be sure to update
+ * {@link com.android.settings.LauncherAppWidgetBinder} as needed.
+ */
+ static final class Favorites implements BaseLauncherColumns {
/**
- * The row ID.
- * <p>Type: INTEGER</p>
+ * The content:// style URL for this table
*/
- static final String ID = "_id";
+ static final Uri CONTENT_URI = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
/**
- * Descriptive name of the favorite that can be displayed to the user.
- * <P>Type: TEXT</P>
+ * The content:// style URL for this table. When this Uri is used, no notification is
+ * sent if the content changes.
*/
- static final String TITLE = "title";
+ static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
/**
- * The Intent URL of the favorite, describing what it points to. This
- * value is given to {@link android.content.Intent#getIntent} to create
- * an Intent that can be launched.
- * <P>Type: TEXT</P>
+ * The content:// style URL for a given row, identified by its id.
+ *
+ * @param id The row id.
+ * @param notify True to send a notification is the content changes.
+ *
+ * @return The unique content URL for the specified row.
*/
- static final String INTENT = "intent";
+ static Uri getContentUri(long id, boolean notify) {
+ return Uri.parse("content://" + LauncherProvider.AUTHORITY +
+ "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" +
+ LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
+ }
/**
* The container holding the favorite
static final String SPANY = "spanY";
/**
- * The type of the favorite
- *
- * <P>Type: INTEGER</P>
- */
- static final String ITEM_TYPE = "itemType";
-
- /**
- * The favorite is an application
- */
- static final int ITEM_TYPE_APPLICATION = 0;
-
- /**
- * The favorite is an application created shortcut
- */
- static final int ITEM_TYPE_SHORTCUT = 1;
-
- /**
* The favorite is a user created folder
*/
static final int ITEM_TYPE_USER_FOLDER = 2;
* value is 1, it is an application-created shortcut.
* <P>Type: INTEGER</P>
*/
+ @Deprecated
static final String IS_SHORTCUT = "isShortcut";
/**
- * The icon type.
- * <P>Type: INTEGER</P>
- */
- static final String ICON_TYPE = "iconType";
-
- /**
- * The icon is a resource identified by a package name and an integer id.
- */
- static final int ICON_TYPE_RESOURCE = 0;
-
- /**
- * The icon is a bitmap.
- */
- static final int ICON_TYPE_BITMAP = 1;
-
- /**
- * The icon package name, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- static final String ICON_PACKAGE = "iconPackage";
-
- /**
- * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- static final String ICON_RESOURCE = "iconResource";
-
- /**
- * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
- * <P>Type: BLOB</P>
- */
- static final String ICON = "icon";
-
- /**
* The URI associated with the favorite. It is used, for instance, by
* live folders to find the content provider.
* <P>Type: TEXT</P>
if (intent != null && name != null) {
final ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.INTENT },
+ new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);