diff options
30 files changed, 1335 insertions, 148 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 39fa9a2..a0d0e5c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -91,6 +91,10 @@ <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 diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml new file mode 100644 index 0000000..4fa9847 --- /dev/null +++ b/res/anim/fade_in_fast.xml @@ -0,0 +1,23 @@ +<?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" /> diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml new file mode 100644 index 0000000..a061a6c --- /dev/null +++ b/res/anim/fade_out_fast.xml @@ -0,0 +1,23 @@ +<?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" /> diff --git a/res/drawable/btn_circle.xml b/res/drawable/btn_circle.xml new file mode 100644 index 0000000..9208010 --- /dev/null +++ b/res/drawable/btn_circle.xml @@ -0,0 +1,32 @@ +<?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> diff --git a/res/drawable/btn_circle_disable.png b/res/drawable/btn_circle_disable.png Binary files differnew file mode 100644 index 0000000..33b74a6 --- /dev/null +++ b/res/drawable/btn_circle_disable.png diff --git a/res/drawable/btn_circle_disable_focused.png b/res/drawable/btn_circle_disable_focused.png Binary files differnew file mode 100644 index 0000000..005ad8d --- /dev/null +++ b/res/drawable/btn_circle_disable_focused.png diff --git a/res/drawable/btn_circle_normal.png b/res/drawable/btn_circle_normal.png Binary files differnew file mode 100644 index 0000000..fc5af1c --- /dev/null +++ b/res/drawable/btn_circle_normal.png diff --git a/res/drawable/btn_circle_pressed.png b/res/drawable/btn_circle_pressed.png Binary files differnew file mode 100644 index 0000000..8f40afd --- /dev/null +++ b/res/drawable/btn_circle_pressed.png diff --git a/res/drawable/btn_circle_selected.png b/res/drawable/btn_circle_selected.png Binary files differnew file mode 100644 index 0000000..c74fac2 --- /dev/null +++ b/res/drawable/btn_circle_selected.png diff --git a/res/drawable/gestures_background.xml b/res/drawable/gestures_background.xml new file mode 100755 index 0000000..34ec051 --- /dev/null +++ b/res/drawable/gestures_background.xml @@ -0,0 +1,20 @@ +<?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" /> diff --git a/res/drawable/ic_btn_round_plus.png b/res/drawable/ic_btn_round_plus.png Binary files differnew file mode 100644 index 0000000..1ec8a95 --- /dev/null +++ b/res/drawable/ic_btn_round_plus.png diff --git a/res/drawable/texture_paper.jpg b/res/drawable/texture_paper.jpg Binary files differnew file mode 100644 index 0000000..27f4fd6 --- /dev/null +++ b/res/drawable/texture_paper.jpg diff --git a/res/layout/gestures.xml b/res/layout/gestures.xml new file mode 100644 index 0000000..d2beaa8 --- /dev/null +++ b/res/layout/gestures.xml @@ -0,0 +1,96 @@ +<?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> diff --git a/res/layout/gestures_settings.xml b/res/layout/gestures_settings.xml new file mode 100644 index 0000000..4b1976f --- /dev/null +++ b/res/layout/gestures_settings.xml @@ -0,0 +1,37 @@ +<?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> diff --git a/res/layout/gestures_settings_item.xml b/res/layout/gestures_settings_item.xml new file mode 100644 index 0000000..3c47cab --- /dev/null +++ b/res/layout/gestures_settings_item.xml @@ -0,0 +1,31 @@ +<?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" /> diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml index 2c578f3..ba78995 100644 --- a/res/layout/rename_folder.xml +++ b/res/layout/rename_folder.xml @@ -21,6 +21,7 @@ android:orientation="vertical"> <TextView + android:id="@+id/label" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/rename_folder_label" diff --git a/res/values/colors.xml b/res/values/colors.xml index f9cb0c5..e1b4843 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -23,6 +23,8 @@ <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> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 4ae6686..b802353 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -16,4 +16,6 @@ <resources> <dimen name="search_widget_inset">19dip</dimen> + <dimen name="gesture_thumbnail_inset">8dip</dimen> + <dimen name="gesture_thumbnail_size">64dip</dimen> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 3e8cb7c..f083c98 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -94,7 +94,9 @@ <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: --> @@ -123,4 +125,31 @@ <!-- 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> diff --git a/res/values/styles.xml b/res/values/styles.xml index 9b06d26..5319bb0 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -60,4 +60,10 @@ <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> diff --git a/src/com/android/launcher/ApplicationInfo.java b/src/com/android/launcher/ApplicationInfo.java index 9bc0950..c9c8c5c 100644 --- a/src/com/android/launcher/ApplicationInfo.java +++ b/src/com/android/launcher/ApplicationInfo.java @@ -61,7 +61,7 @@ class ApplicationInfo extends ItemInfo { Intent.ShortcutIconResource iconResource; ApplicationInfo() { - itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } public ApplicationInfo(ApplicationInfo info) { @@ -80,7 +80,7 @@ class ApplicationInfo extends ItemInfo { /** * 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 @@ -90,7 +90,7 @@ class ApplicationInfo extends ItemInfo { intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setComponent(className); intent.setFlags(launchFlags); - itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; } @Override @@ -98,22 +98,24 @@ class ApplicationInfo extends ItemInfo { 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); } } } diff --git a/src/com/android/launcher/GesturesActivity.java b/src/com/android/launcher/GesturesActivity.java new file mode 100644 index 0000000..a112e1b --- /dev/null +++ b/src/com/android/launcher/GesturesActivity.java @@ -0,0 +1,310 @@ +/* + * 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; + } + } +} diff --git a/src/com/android/launcher/GesturesConstants.java b/src/com/android/launcher/GesturesConstants.java new file mode 100644 index 0000000..3151ea3 --- /dev/null +++ b/src/com/android/launcher/GesturesConstants.java @@ -0,0 +1,25 @@ +/* + * 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; +} diff --git a/src/com/android/launcher/GesturesPanel.java b/src/com/android/launcher/GesturesPanel.java new file mode 100644 index 0000000..ee39613 --- /dev/null +++ b/src/com/android/launcher/GesturesPanel.java @@ -0,0 +1,47 @@ +/* + * 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); + } +} diff --git a/src/com/android/launcher/ItemInfo.java b/src/com/android/launcher/ItemInfo.java index 51449a7..71cee18 100644 --- a/src/com/android/launcher/ItemInfo.java +++ b/src/com/android/launcher/ItemInfo.java @@ -76,6 +76,11 @@ class ItemInfo { */ int spanY = 1; + /** + * Indicates whether the item is a gesture. + */ + boolean isGesture = false; + ItemInfo() { } @@ -96,13 +101,15 @@ class 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) { diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java index b4437d4..0c54382 100644 --- a/src/com/android/launcher/Launcher.java +++ b/src/com/android/launcher/Launcher.java @@ -41,6 +41,8 @@ import android.content.res.Resources; 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; @@ -58,7 +60,6 @@ import android.text.Selection; 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; @@ -67,6 +68,8 @@ import android.view.Menu; 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; @@ -74,8 +77,16 @@ import android.widget.GridView; 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; @@ -92,6 +103,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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; @@ -100,7 +112,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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; @@ -109,6 +122,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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"; @@ -154,6 +170,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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(); @@ -164,6 +182,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static WallpaperIntentReceiver sWallpaperReceiver; + private static GestureLibrary sLibrary; + private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver(); private final ContentObserver mObserver = new FavoritesChangeObserver(); @@ -202,11 +222,26 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); @@ -308,13 +343,17 @@ public final class Launcher extends Activity implements View.OnClickListener, On // 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); @@ -331,6 +370,16 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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) { @@ -358,10 +407,20 @@ public final class Launcher extends Activity implements View.OnClickListener, On @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) { @@ -448,6 +507,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On mFolderInfo = sModel.getFolderById(this, id); mRestoring = true; } + + mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_PENDING_GESTURE); } /** @@ -495,6 +556,68 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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; } /** @@ -545,14 +668,20 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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) { @@ -568,8 +697,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On itemInfo.icon = activityInfo.loadIcon(packageManager); itemInfo.container = ItemInfo.NO_ID; - mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst); + return itemInfo; } + + return null; } /** @@ -653,6 +784,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); @@ -660,7 +799,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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)); @@ -668,9 +807,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); @@ -694,8 +833,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On info.customIcon = customIcon; info.iconResource = iconResource; - LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); return info; } @@ -723,15 +860,15 @@ public final class Launcher extends Activity implements View.OnClickListener, On // 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); @@ -743,6 +880,74 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } + 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 @@ -791,6 +996,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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 @@ -911,6 +1120,11 @@ public final class Launcher extends Activity implements View.OnClickListener, On .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); @@ -1028,7 +1242,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); @@ -1039,9 +1253,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); } } @@ -1482,7 +1696,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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); } @@ -1601,6 +1815,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On return sModel; } + static GestureLibrary getGestureLibrary() { + return sLibrary; + } + void closeAllApplications() { mDrawer.close(); } @@ -1669,6 +1887,26 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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; @@ -1789,26 +2027,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On 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; } @@ -2109,5 +2328,113 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } } + + 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); + } + } + } } diff --git a/src/com/android/launcher/LauncherModel.java b/src/com/android/launcher/LauncherModel.java index 19f6e9b..271f9f4 100644 --- a/src/com/android/launcher/LauncherModel.java +++ b/src/com/android/launcher/LauncherModel.java @@ -560,7 +560,7 @@ public class LauncherModel { } } - 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()); } @@ -614,11 +614,11 @@ public class LauncherModel { 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); @@ -725,7 +725,7 @@ public class LauncherModel { 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); @@ -1143,7 +1143,7 @@ public class LauncherModel { /** * 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(); @@ -1154,11 +1154,11 @@ public class LauncherModel { 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(); } @@ -1172,16 +1172,16 @@ public class LauncherModel { 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; } @@ -1326,6 +1326,26 @@ public class LauncherModel { } /** + * 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) { @@ -1359,4 +1379,84 @@ public class LauncherModel { 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; + } } diff --git a/src/com/android/launcher/LauncherProvider.java b/src/com/android/launcher/LauncherProvider.java index a27b746..ba8ebda 100644 --- a/src/com/android/launcher/LauncherProvider.java +++ b/src/com/android/launcher/LauncherProvider.java @@ -55,7 +55,7 @@ public class LauncherProvider extends ContentProvider { 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"; @@ -63,10 +63,11 @@ public class LauncherProvider extends ContentProvider { 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. */ @@ -99,7 +100,7 @@ public class LauncherProvider extends ContentProvider { 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); @@ -220,6 +221,17 @@ public class LauncherProvider extends ContentProvider { "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(); @@ -270,7 +282,7 @@ public class LauncherProvider extends ContentProvider { } 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); @@ -289,7 +301,7 @@ public class LauncherProvider extends ContentProvider { 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)); @@ -352,6 +364,29 @@ public class LauncherProvider extends ContentProvider { 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."); diff --git a/src/com/android/launcher/LauncherSettings.java b/src/com/android/launcher/LauncherSettings.java index 60ea0df..062c8a6 100644 --- a/src/com/android/launcher/LauncherSettings.java +++ b/src/com/android/launcher/LauncherSettings.java @@ -23,16 +23,79 @@ import android.net.Uri; * 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"); /** @@ -40,7 +103,7 @@ class LauncherSettings { * 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"); /** @@ -53,29 +116,44 @@ class LauncherSettings { */ 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 @@ -121,23 +199,6 @@ class LauncherSettings { 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; @@ -180,43 +241,10 @@ class LauncherSettings { * 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> diff --git a/src/com/android/launcher/UninstallShortcutReceiver.java b/src/com/android/launcher/UninstallShortcutReceiver.java index 334fbc2..bf71815 100644 --- a/src/com/android/launcher/UninstallShortcutReceiver.java +++ b/src/com/android/launcher/UninstallShortcutReceiver.java @@ -35,7 +35,7 @@ public class UninstallShortcutReceiver extends BroadcastReceiver { 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); |