diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 21 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherBackupAgentHelper.java | 46 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherBackupHelper.java (renamed from src/com/android/launcher3/LauncherBackupAgent.java) | 254 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherProvider.java | 2 | ||||
-rw-r--r-- | src/com/android/launcher3/PagedView.java | 55 | ||||
-rw-r--r-- | src/com/android/launcher3/TranslucentDecor.java | 82 | ||||
-rw-r--r-- | src/com/android/launcher3/TransparentBars.java | 54 | ||||
-rw-r--r-- | src/com/android/launcher3/WallpaperCropActivity.java | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/WallpaperPickerActivity.java | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/Workspace.java | 78 |
10 files changed, 383 insertions, 217 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 45c95736e..cfa554513 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -327,7 +327,7 @@ public class Launcher extends Activity private BubbleTextView mWaitingForResume; - protected TransparentBars mTransparentBars; + protected TranslucentDecor mTransparentDecor; private HideFromAccessibilityHelper mHideFromAccessibilityHelper = new HideFromAccessibilityHelper(); @@ -409,6 +409,7 @@ public class Launcher extends Activity mStats = new Stats(this); mAppWidgetManager = AppWidgetManager.getInstance(this); + mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); mAppWidgetHost.startListening(); @@ -422,11 +423,12 @@ public class Launcher extends Activity Environment.getExternalStorageDirectory() + "/launcher"); } + checkForLocaleChange(); setContentView(R.layout.launcher); - mTransparentBars = new TransparentBars(findViewById(R.id.launcher)); - mTransparentBars.requestTransparentBars(true); + mTransparentDecor = new TranslucentDecor(findViewById(R.id.launcher)); + mTransparentDecor.requestTranslucentDecor(true); setupViews(); grid.layout(this); @@ -921,8 +923,8 @@ public class Launcher extends Activity mWorkspace.getCustomContentCallbacks().onShow(); } } - mWorkspace.updateInteractionForState(); + mWorkspace.onResume(); } @Override @@ -985,14 +987,9 @@ public class Launcher extends Activity public void setScrollY(int scrollY); } - // Add a fullscreen unpadded view to the workspace to the left all other screens. - public QSBScroller addToCustomContentPage(View customContent) { - return addToCustomContentPage(customContent, null); - } - public QSBScroller addToCustomContentPage(View customContent, - CustomContentCallbacks callbacks) { - mWorkspace.addToCustomContentPage(customContent, callbacks); + CustomContentCallbacks callbacks, String description) { + mWorkspace.addToCustomContentPage(customContent, callbacks, description); return mQsbScroller; } @@ -3466,7 +3463,7 @@ public class Launcher extends Activity text.clear(); // Populate event with a fake title based on the current state. if (mState == State.APPS_CUSTOMIZE) { - text.add(getString(R.string.all_apps_button_label)); + text.add(mAppsCustomizeTabHost.getCurrentTabView().getContentDescription()); } else { text.add(getString(R.string.all_apps_home_button_label)); } diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java new file mode 100644 index 000000000..2b5059b72 --- /dev/null +++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 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.launcher3; + +import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupManager; +import android.content.Context; + +public class LauncherBackupAgentHelper extends BackupAgentHelper { + + private static BackupManager sBackupManager; + + /** + * Notify the backup manager that out database is dirty. + * + * <P>This does not force an immediate backup. + * + * @param context application context + */ + public static void dataChanged(Context context) { + if (sBackupManager == null) { + sBackupManager = new BackupManager(context); + } + sBackupManager.dataChanged(); + } + + + @Override + public void onCreate() { + addHelper(LauncherBackupHelper.LAUNCHER_PREFIX, new LauncherBackupHelper(this)); + } +} diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupHelper.java index f40238cec..9b901eea1 100644 --- a/src/com/android/launcher3/LauncherBackupAgent.java +++ b/src/com/android/launcher3/LauncherBackupHelper.java @@ -30,7 +30,8 @@ import com.android.launcher3.backup.BackupProtos.Resource; import com.android.launcher3.backup.BackupProtos.Screen; import com.android.launcher3.backup.BackupProtos.Widget; -import android.app.backup.BackupAgent; +import android.app.backup.BackupDataInputStream; +import android.app.backup.BackupHelper; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupManager; @@ -64,10 +65,11 @@ import java.util.zip.CRC32; /** * Persist the launcher home state across calamities. */ -public class LauncherBackupAgent extends BackupAgent { +public class LauncherBackupHelper implements BackupHelper { - private static final String TAG = "LauncherBackupAgent"; + private static final String TAG = "LauncherBackupHelper"; private static final boolean DEBUG = false; + private static final boolean DEBUG_PAYLOAD = false; private static final int MAX_JOURNAL_SIZE = 1000000; @@ -79,6 +81,8 @@ public class LauncherBackupAgent extends BackupAgent { public static final int IMAGE_COMPRESSION_QUALITY = 75; + public static final String LAUNCHER_PREFIX = "L"; + private static final Bitmap.CompressFormat IMAGE_FORMAT = android.graphics.Bitmap.CompressFormat.PNG; @@ -130,26 +134,19 @@ public class LauncherBackupAgent extends BackupAgent { private static final int SCREEN_RANK_INDEX = 2; - - private static final String[] ICON_PROJECTION = { - Favorites._ID, // 0 - Favorites.MODIFIED, // 1 - Favorites.INTENT // 2 - }; + private final Context mContext; private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap; + private ArrayList<Key> mKeys; - /** - * Notify the backup manager that out database is dirty. - * - * <P>This does not force an immediate backup. - * - * @param context application context - */ - public static void dataChanged(Context context) { + public LauncherBackupHelper(Context context) { + mContext = context; + } + + private void dataChanged() { if (sBackupManager == null) { - sBackupManager = new BackupManager(context); + sBackupManager = new BackupManager(mContext); } sBackupManager.dataChanged(); } @@ -167,9 +164,8 @@ public class LauncherBackupAgent extends BackupAgent { * @throws IOException */ @Override - public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) - throws IOException { + public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { Log.v(TAG, "onBackup"); Journal in = readJournal(oldState); @@ -183,10 +179,14 @@ public class LauncherBackupAgent extends BackupAgent { Log.v(TAG, "lastBackupTime=" + lastBackupTime); ArrayList<Key> keys = new ArrayList<Key>(); - backupFavorites(in, data, out, keys); - backupScreens(in, data, out, keys); - backupIcons(in, data, out, keys); - backupWidgets(in, data, out, keys); + try { + backupFavorites(in, data, out, keys); + backupScreens(in, data, out, keys); + backupIcons(in, data, out, keys); + backupWidgets(in, data, out, keys); + } catch (IOException e) { + Log.e(TAG, "launcher backup has failed", e); + } out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY); writeJournal(newState, out); @@ -194,70 +194,76 @@ public class LauncherBackupAgent extends BackupAgent { } /** - * Restore home screen from the restored data stream. + * Restore launcher configuration from the restored data stream. * * <P>Keys may arrive in any order. * - * @param data the key/value pairs from the server - * @param versionCode the version of the app that generated the data - * @param newState notes for the next backup - * @throws IOException + * @param data the key/value pair from the server */ @Override - public void onRestore(BackupDataInput data, int versionCode, ParcelFileDescriptor newState) - throws IOException { - Log.v(TAG, "onRestore"); - int numRows = 0; - Journal out = new Journal(); - - ArrayList<Key> keys = new ArrayList<Key>(); + public void restoreEntity(BackupDataInputStream data) { + Log.v(TAG, "restoreEntity"); + if (mKeys == null) { + mKeys = new ArrayList<Key>(); + } byte[] buffer = new byte[512]; - while (data.readNextHeader()) { - numRows++; String backupKey = data.getKey(); - int dataSize = data.getDataSize(); + int dataSize = data.size(); if (buffer.length < dataSize) { buffer = new byte[dataSize]; } Key key = null; - int bytesRead = data.readEntityData(buffer, 0, dataSize); - if (DEBUG) { - Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available"); - } - try { - key = backupKeyToKey(backupKey); - switch (key.type) { - case Key.FAVORITE: - restoreFavorite(key, buffer, dataSize, keys); - break; - - case Key.SCREEN: - restoreScreen(key, buffer, dataSize, keys); - break; - - case Key.ICON: - restoreIcon(key, buffer, dataSize, keys); - break; - - case Key.WIDGET: - restoreWidget(key, buffer, dataSize, keys); - break; - - default: - Log.w(TAG, "unknown restore entity type: " + key.type); - break; - } - } catch (KeyParsingException e) { - Log.w(TAG, "ignoring unparsable backup key: " + backupKey); + int bytesRead = 0; + try { + bytesRead = data.read(buffer, 0, dataSize); + if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available"); + } catch (IOException e) { + Log.d(TAG, "failed to read entity from restore data", e); + } + try { + key = backupKeyToKey(backupKey); + switch (key.type) { + case Key.FAVORITE: + restoreFavorite(key, buffer, dataSize, mKeys); + break; + + case Key.SCREEN: + restoreScreen(key, buffer, dataSize, mKeys); + break; + + case Key.ICON: + restoreIcon(key, buffer, dataSize, mKeys); + break; + + case Key.WIDGET: + restoreWidget(key, buffer, dataSize, mKeys); + break; + + default: + Log.w(TAG, "unknown restore entity type: " + key.type); + break; } + } catch (KeyParsingException e) { + Log.w(TAG, "ignoring unparsable backup key: " + backupKey); } + } + + /** + * Record the restore state for the next backup. + * + * @param newState notes about the backup state after restore. + */ + @Override + public void writeNewStateDescription(ParcelFileDescriptor newState) { // clear the output journal time, to force a full backup to // will catch any changes the restore process might have made + Journal out = new Journal(); out.t = 0; - out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY); + out.key = mKeys.toArray(BackupProtos.Key.EMPTY_ARRAY); writeJournal(newState, out); - Log.v(TAG, "onRestore: read " + numRows + " rows"); + Log.v(TAG, "onRestore: read " + mKeys.size() + " rows"); + mKeys.clear(); } /** @@ -278,7 +284,7 @@ public class LauncherBackupAgent extends BackupAgent { if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size()); // persist things that have changed since the last backup - ContentResolver cr = getContentResolver(); + ContentResolver cr = mContext.getContentResolver(); Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null); Set<String> currentIds = new HashSet<String>(cursor.getCount()); @@ -346,7 +352,7 @@ public class LauncherBackupAgent extends BackupAgent { if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size()); // persist things that have changed since the last backup - ContentResolver cr = getContentResolver(); + ContentResolver cr = mContext.getContentResolver(); Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION, null, null, null); Set<String> currentIds = new HashSet<String>(cursor.getCount()); @@ -408,15 +414,15 @@ public class LauncherBackupAgent extends BackupAgent { private void backupIcons(Journal in, BackupDataOutput data, Journal out, ArrayList<Key> keys) throws IOException { // persist icons that haven't been persisted yet - final LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app == null) { - dataChanged(this); // try again later + final LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); + if (appState == null) { + dataChanged(); // try again later if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup"); return; } - final ContentResolver cr = getContentResolver(); - final IconCache iconCache = app.getIconCache(); - final int dpi = getResources().getDisplayMetrics().densityDpi; + final ContentResolver cr = mContext.getContentResolver(); + final IconCache iconCache = appState.getIconCache(); + final int dpi = mContext.getResources().getDisplayMetrics().densityDpi; // read the old ID set Set<String> savedIds = getSavedIdsByType(Key.ICON, in); @@ -463,7 +469,7 @@ public class LauncherBackupAgent extends BackupAgent { } else { if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey); // too many icons for this pass, request another. - dataChanged(this); + dataChanged(); } } } catch (URISyntaxException e) { @@ -527,15 +533,15 @@ public class LauncherBackupAgent extends BackupAgent { // persist static widget info that hasn't been persisted yet final LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); if (appState == null) { - dataChanged(this); // try again later + dataChanged(); // try again later if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup"); return; } - final ContentResolver cr = getContentResolver(); - final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(this); - final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(this); + final ContentResolver cr = mContext.getContentResolver(); + final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext); + final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext); final IconCache iconCache = appState.getIconCache(); - final int dpi = getResources().getDisplayMetrics().densityDpi; + final int dpi = mContext.getResources().getDisplayMetrics().densityDpi; final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile(); if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx); @@ -584,7 +590,7 @@ public class LauncherBackupAgent extends BackupAgent { } else { if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey); // too many widgets for this pass, request another. - dataChanged(this); + dataChanged(); } } } @@ -812,7 +818,7 @@ public class LauncherBackupAgent extends BackupAgent { if (info.icon != 0) { widget.icon = new Resource(); Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon); - Bitmap icon = Utilities.createIconBitmap(fullResIcon, this); + Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext); ByteArrayOutputStream os = new ByteArrayOutputStream(); if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) { widget.icon.data = os.toByteArray(); @@ -849,43 +855,49 @@ public class LauncherBackupAgent extends BackupAgent { * @return a Journal protocol bugffer */ private Journal readJournal(ParcelFileDescriptor oldState) { - int fileSize = (int) oldState.getStatSize(); - int remaining = fileSize; - byte[] buffer = null; Journal journal = new Journal(); - if (remaining < MAX_JOURNAL_SIZE) { - FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor()); - int offset = 0; - - buffer = new byte[remaining]; - while (remaining > 0) { + if (oldState == null) { + return journal; + } + FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor()); + try { + int remaining = inStream.available(); + if (DEBUG) Log.d(TAG, "available " + remaining); + if (remaining < MAX_JOURNAL_SIZE) { + byte[] buffer = new byte[remaining]; int bytesRead = 0; - try { - bytesRead = inStream.read(buffer, offset, remaining); - } catch (IOException e) { - Log.w(TAG, "failed to read the journal", e); - buffer = null; - remaining = 0; - } - if (bytesRead > 0) { - remaining -= bytesRead; - } else { - // act like there is not journal - Log.w(TAG, "failed to read the journal"); - buffer = null; - remaining = 0; + while (remaining > 0) { + try { + int result = inStream.read(buffer, bytesRead, remaining); + if (result > 0) { + if (DEBUG) Log.d(TAG, "read some bytes: " + result); + remaining -= result; + bytesRead += result; + } else { + // stop reading ands see what there is to parse + Log.w(TAG, "read error: " + result); + remaining = 0; + } + } catch (IOException e) { + Log.w(TAG, "failed to read the journal", e); + buffer = null; + remaining = 0; + } } - } - - if (buffer != null) { - try { - MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, fileSize)); - } catch (InvalidProtocolBufferNanoException e) { - Log.d(TAG, "failed to read the journal", e); - journal.clear(); + if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead); + + if (buffer != null) { + try { + MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead)); + } catch (InvalidProtocolBufferNanoException e) { + Log.d(TAG, "failed to read the journal", e); + journal.clear(); + } } } - + } catch (IOException e) { + Log.d(TAG, "failed to close the journal", e); + } finally { try { inStream.close(); } catch (IOException e) { @@ -904,7 +916,7 @@ public class LauncherBackupAgent extends BackupAgent { out.bytes += blob.length; Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " + getKeyName(key) + "/" + blob.length); - if(DEBUG) { + if(DEBUG_PAYLOAD) { String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP); final int chunkSize = 1024; for (int offset = 0; offset < encoded.length(); offset += chunkSize) { @@ -983,7 +995,7 @@ public class LauncherBackupAgent extends BackupAgent { private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) { if (mWidgetMap == null) { List<AppWidgetProviderInfo> widgets = - AppWidgetManager.getInstance(this).getInstalledProviders(); + AppWidgetManager.getInstance(mContext).getInstalledProviders(); mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size()); for (AppWidgetProviderInfo info : widgets) { mWidgetMap.put(info.provider, info); diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 5a8f630bc..39afe1042 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -210,7 +210,7 @@ public class LauncherProvider extends ContentProvider { } // always notify the backup agent - LauncherBackupAgent.dataChanged(getContext()); + LauncherBackupAgentHelper.dataChanged(getContext()); } private void addModifiedTime(ContentValues values) { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index c8e34dda1..96d8c1928 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -32,6 +32,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -334,6 +335,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // Hook up the page indicator ViewGroup parent = (ViewGroup) getParent(); if (mPageIndicator == null && mPageIndicatorViewId > -1) { @@ -347,9 +350,19 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations); + mPageIndicator.setOnClickListener(getPageIndicatorClickListener()); + mPageIndicator.setContentDescription(getPageIndicatorDescription()); } } + protected String getPageIndicatorDescription() { + return getCurrentPageDescription(); + } + + protected OnClickListener getPageIndicatorClickListener() { + return null; + } + protected void onDetachedFromWindow() { // Unhook the page indicator mPageIndicator = null; @@ -649,6 +662,28 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } + private void sendScrollAccessibilityEvent() { + AccessibilityManager am = + (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (am.isEnabled()) { + AccessibilityEvent ev = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.getText().add(""); + ev.setItemCount(getChildCount()); + ev.setFromIndex(mCurrentPage); + int action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; + + if (getNextPage() >= mCurrentPage) { + action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; + } else { + action = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; + } + + ev.setAction(action); + sendAccessibilityEventUnchecked(ev); + } + } + // we moved this functionality to a helper function so SmoothPagedView can reuse it protected boolean computeScrollHelper() { if (mScroller.computeScrollOffset()) { @@ -663,6 +698,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc invalidate(); return true; } else if (mNextPage != INVALID_PAGE) { + sendScrollAccessibilityEvent(); + mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); mNextPage = INVALID_PAGE; notifyPageSwitchListener(); @@ -680,14 +717,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } onPostReorderingAnimationCompleted(); - // Notify the user when the page changes - AccessibilityManager accessibilityManager = (AccessibilityManager) + AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent ev = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); - ev.getText().add(getCurrentPageDescription()); - sendAccessibilityEventUnchecked(ev); + if (am.isEnabled()) { + // Notify the user when the page changes + announceForAccessibility(getCurrentPageDescription()); } return true; } @@ -2133,6 +2167,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc focusedChild.clearFocus(); } + sendScrollAccessibilityEvent(); + pageBeginMoving(); awakenScrollBars(duration); if (immediate) { @@ -2719,11 +2755,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setScrollable(true); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - event.setFromIndex(mCurrentPage); - event.setToIndex(mCurrentPage); - event.setItemCount(getChildCount()); - } } @Override diff --git a/src/com/android/launcher3/TranslucentDecor.java b/src/com/android/launcher3/TranslucentDecor.java new file mode 100644 index 000000000..b50c02268 --- /dev/null +++ b/src/com/android/launcher3/TranslucentDecor.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 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.launcher3; + +import android.app.Activity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +public class TranslucentDecor { + private static final int SYSTEM_UI_FLAG_TRANSPARENT_STATUS = 0x00001000; + private static final int SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION = 0x00002000; + + // Replace with SDK constants when available. + public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000; + public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000; + + // Behave properly on early K builds. + public static final boolean SYSUI_SUPPORTED = !hasSystemUiFlag("ALLOW_TRANSIENT") && + hasSystemUiFlag("TRANSPARENT_STATUS") && + hasSystemUiFlag("TRANSPARENT_NAVIGATION"); + + public static final boolean WM_SUPPORTED = + hasWindowManagerFlag("TRANSLUCENT_STATUS") && + hasWindowManagerFlag("TRANSLUCENT_NAVIGATION"); + + private final View mTarget; + + public TranslucentDecor(View target) { + mTarget = target; + } + + public void requestTranslucentDecor(boolean translucent) { + int sysui = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + if (WM_SUPPORTED && mTarget.getContext() instanceof Activity) { + Window w = ((Activity) mTarget.getContext()).getWindow(); + int wmFlags = FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION; + if (translucent) { + w.addFlags(wmFlags); + } else { + w.clearFlags(wmFlags); + } + } else if (SYSUI_SUPPORTED) { // Remove when droidfood platform is updated + if (translucent) { + sysui |= SYSTEM_UI_FLAG_TRANSPARENT_STATUS | SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION; + } + } + mTarget.setSystemUiVisibility(sysui); + } + + private static boolean hasWindowManagerFlag(String name) { + try { + return WindowManager.LayoutParams.class.getField("FLAG_" + name) != null; + } catch (NoSuchFieldException e) { + return false; + } + } + + private static boolean hasSystemUiFlag(String name) { + try { + return View.class.getField("SYSTEM_UI_FLAG_" + name) != null; + } catch (NoSuchFieldException e) { + return false; + } + } +} diff --git a/src/com/android/launcher3/TransparentBars.java b/src/com/android/launcher3/TransparentBars.java deleted file mode 100644 index a12da9e6f..000000000 --- a/src/com/android/launcher3/TransparentBars.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 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.launcher3; - -import android.view.View; - -public class TransparentBars { - private static final int SYSTEM_UI_FLAG_TRANSPARENT_STATUS = 0x00001000; - private static final int SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION = 0x00002000; - - // Behave properly on early K builds. Replace with api check once sdk is baked. - public static final boolean SUPPORTED = !hasSystemUiFlag("ALLOW_TRANSIENT") - && hasSystemUiFlag("TRANSPARENT_STATUS") - && hasSystemUiFlag("TRANSPARENT_NAVIGATION"); - - private final View mTarget; - - public TransparentBars(View target) { - mTarget = target; - } - - public void requestTransparentBars(boolean transparent) { - if (!SUPPORTED) return; - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - if (transparent) { - flags |= SYSTEM_UI_FLAG_TRANSPARENT_STATUS | SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION; - } - mTarget.setSystemUiVisibility(flags); - } - - private static boolean hasSystemUiFlag(String name) { - try { - return View.class.getField("SYSTEM_UI_FLAG_" + name) != null; - } catch (NoSuchFieldException e) { - return false; - } - } -} diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java index 703db9a94..78c8964b6 100644 --- a/src/com/android/launcher3/WallpaperCropActivity.java +++ b/src/com/android/launcher3/WallpaperCropActivity.java @@ -102,8 +102,8 @@ public class WallpaperCropActivity extends Activity { cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); } }); - TransparentBars transparentBars = new TransparentBars(findViewById(R.id.wallpaper_root)); - transparentBars.requestTransparentBars(true); + TranslucentDecor transparentDecor = new TranslucentDecor(findViewById(R.id.wallpaper_root)); + transparentDecor.requestTranslucentDecor(true); } public boolean enableRotation() { diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java index 9702a3a67..82c9977c7 100644 --- a/src/com/android/launcher3/WallpaperPickerActivity.java +++ b/src/com/android/launcher3/WallpaperPickerActivity.java @@ -181,8 +181,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { protected void init() { setContentView(R.layout.wallpaper_picker); final WallpaperRootView root = (WallpaperRootView) findViewById(R.id.wallpaper_root); - TransparentBars transparentBars = new TransparentBars(root); - transparentBars.requestTransparentBars(true); + TranslucentDecor transparentDecor = new TranslucentDecor(root); + transparentDecor.requestTranslucentDecor(true); mCropView = (CropView) findViewById(R.id.cropView); mWallpaperStrip = findViewById(R.id.wallpaper_strip); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 5a8472c01..a81ada831 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -44,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.IBinder; import android.os.Parcelable; +import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -52,6 +53,9 @@ import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.TextView; @@ -113,6 +117,7 @@ public class Workspace extends SmoothPagedView private int mDefaultPage; private ShortcutAndWidgetContainer mDragSourceInternal; + private static boolean sAccessibilityEnabled; // The screen id used for the empty screen always present to the right. private final static long EXTRA_EMPTY_SCREEN_ID = -201; @@ -139,6 +144,7 @@ public class Workspace extends SmoothPagedView CustomContentCallbacks mCustomContentCallbacks; boolean mCustomContentShowing; private float mLastCustomContentScrollProgress = -1f; + private String mCustomContentDescription = ""; /** * The CellLayout that is currently being dragged over @@ -318,11 +324,7 @@ public class Workspace extends SmoothPagedView // Disable multitouch across the workspace/all apps/customize tray setMotionEventSplittingEnabled(true); - - // Unless otherwise specified this view is important for accessibility. - if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } @Override @@ -450,9 +452,7 @@ public class Workspace extends SmoothPagedView CellLayout cl = ((CellLayout) child); cl.setOnInterceptTouchListener(this); cl.setClickable(true); - cl.setContentDescription(getContext().getString( - R.string.workspace_description_format, getChildCount())); - + cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO); super.onChildViewAdded(parent, child); } @@ -555,7 +555,8 @@ public class Workspace extends SmoothPagedView setCurrentPage(getCurrentPage() - 1); } - public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks) { + public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, + String description) { if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) { throw new RuntimeException("Expected custom content screen to exist"); } @@ -570,7 +571,9 @@ public class Workspace extends SmoothPagedView if (customContent instanceof Insettable) { ((Insettable)customContent).setInsets(mInsets); } + customScreen.removeAllViews(); customScreen.addViewToCellLayout(customContent, 0, 0, lp, true); + mCustomContentDescription = description; mCustomContentCallbacks = callbacks; } @@ -642,7 +645,6 @@ public class Workspace extends SmoothPagedView return newId; } - public CellLayout getScreenWithId(long screenId) { CellLayout layout = mWorkspaceScreens.get(screenId); return layout; @@ -1039,6 +1041,9 @@ public class Workspace extends SmoothPagedView mLauncher.updateVoiceButtonProxyVisible(false); } } + if (getPageIndicator() != null) { + getPageIndicator().setContentDescription(getPageIndicatorDescription()); + } } protected CustomContentCallbacks getCustomContentCallbacks() { @@ -1412,6 +1417,22 @@ public class Workspace extends SmoothPagedView } @Override + protected OnClickListener getPageIndicatorClickListener() { + AccessibilityManager am = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (!am.isTouchExplorationEnabled()) { + return null; + } + OnClickListener listener = new OnClickListener() { + @Override + public void onClick(View arg0) { + enterOverviewMode(); + } + }; + return listener; + } + + @Override protected void screenScrolled(int screenCenter) { final boolean isRtl = isLayoutRtl(); super.screenScrolled(screenCenter); @@ -1475,6 +1496,17 @@ public class Workspace extends SmoothPagedView mWindowToken = null; } + protected void onResume() { + if (getPageIndicator() != null) { + // In case accessibility state has changed, we need to perform this on every + // attach to window + getPageIndicator().setOnClickListener(getPageIndicatorClickListener()); + } + AccessibilityManager am = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + sAccessibilityEnabled = am.isEnabled(); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { @@ -1852,6 +1884,14 @@ public class Workspace extends SmoothPagedView private void setState(State state) { mState = state; updateInteractionForState(); + updateAccessibilityFlags(); + } + + private void updateAccessibilityFlags() { + int accessible = mState == State.NORMAL ? + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES : + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; + setImportantForAccessibility(accessible); } Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) { @@ -2036,8 +2076,11 @@ public class Workspace extends SmoothPagedView } public static void updateVisibility(View view) { - if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != INVISIBLE) { - view.setVisibility(INVISIBLE); + // We want to avoid the extra layout pass by setting the views to GONE unless + // accessibility is on, in which case not setting them to GONE causes a glitch. + int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE; + if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) { + view.setVisibility(invisibleState); } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != VISIBLE) { view.setVisibility(VISIBLE); @@ -4308,10 +4351,19 @@ public class Workspace extends SmoothPagedView public void syncPageItems(int page, boolean immediate) { } + protected String getPageIndicatorDescription() { + String settings = getResources().getString(R.string.settings_button_text); + return getCurrentPageDescription() + ", " + settings; + } + protected String getCurrentPageDescription() { int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + int delta = numCustomPages(); + if (hasCustomContent() && getNextPage() == 0) { + return mCustomContentDescription; + } return String.format(getContext().getString(R.string.workspace_scroll_format), - page + 1, getChildCount()); + page + 1 - delta, getChildCount() - delta); } public void getLocationInDragLayer(int[] loc) { |