Add gestures to Home.

Press the Home key while in Home to enable the gestures pad.
This commit is contained in:
Romain Guy
2009-06-09 12:57:21 -07:00
parent cbb89e4fc2
commit 73b979d8c1
30 changed files with 1337 additions and 150 deletions
+4
View File
@@ -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
+23
View File
@@ -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" />
+23
View File
@@ -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" />
+32
View File
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

+20
View File
@@ -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" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

+96
View File
@@ -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>
+37
View File
@@ -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>
+31
View File
@@ -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" />
+1
View File
@@ -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"
+4 -2
View File
@@ -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>
+2
View File
@@ -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>
+30 -1
View File
@@ -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>
+6
View File
@@ -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>
+13 -11
View File
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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;
}
@@ -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);
}
}
+14 -7
View File
@@ -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) {
+368 -41
View File
@@ -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,9 +407,19 @@ 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
@@ -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);
}
}
}
}
+110 -10
View File
@@ -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;
}
@@ -1325,6 +1325,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.
*/
@@ -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;
}
}
+40 -5
View File
@@ -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.");
+100 -72
View File
@@ -23,11 +23,109 @@ import android.net.Uri;
* Settings related utilities.
*/
class LauncherSettings {
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_GESTURES +
"?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
/**
* The content:// style URL for this table. When this Uri is used, no notification is
* sent if the content changes.
*/
static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
"?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
/**
* 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 Uri getContentUri(long id, boolean notify) {
return Uri.parse("content://" + LauncherProvider.AUTHORITY +
"/" + 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 BaseColumns {
static final class Favorites implements BaseLauncherColumns {
/**
* The content:// style URL for this table
*/
@@ -57,26 +155,6 @@ class LauncherSettings {
LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
}
/**
* The row ID.
* <p>Type: INTEGER</p>
*/
static final String ID = "_id";
/**
* Descriptive name of the favorite that can be displayed to the user.
* <P>Type: TEXT</P>
*/
static final String TITLE = "title";
/**
* 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>
*/
static final String INTENT = "intent";
/**
* The container holding the favorite
* <P>Type: INTEGER</P>
@@ -120,23 +198,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
*/
@@ -180,42 +241,9 @@ 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.
@@ -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);