diff options
Diffstat (limited to 'samples/ApiDemos/src')
55 files changed, 5155 insertions, 621 deletions
diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/AccessibilityNodeProviderActivity.java b/samples/ApiDemos/src/com/example/android/apis/accessibility/AccessibilityNodeProviderActivity.java deleted file mode 100644 index 1ca036a55..000000000 --- a/samples/ApiDemos/src/com/example/android/apis/accessibility/AccessibilityNodeProviderActivity.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (C) 2011 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.example.android.apis.accessibility; - -import com.example.android.apis.R; - -import android.app.Activity; -import android.app.Service; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This sample demonstrates how a View can expose a virtual view sub-tree - * rooted at it. A virtual sub-tree is composed of imaginary Views - * that are reported as a part of the view hierarchy for accessibility - * purposes. This enables custom views that draw complex content to report - * them selves as a tree of virtual views, thus conveying their logical - * structure. - * <p> - * For example, a View may draw a monthly calendar as a grid of days while - * each such day may contains some events. From a perspective of the View - * hierarchy the calendar is composed of a single View but an accessibility - * service would benefit of traversing the logical structure of the calendar - * by examining each day and each event on that day. - * </p> - */ -public class AccessibilityNodeProviderActivity extends Activity { - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.accessibility_node_provider); - } - - /** - * This class presents a View that is composed of three virtual children - * each of which is drawn with a different color and represents a region - * of the View that has different semantics compared to other such regions. - * While the virtual view tree exposed by this class is one level deep - * for simplicity, there is no bound on the complexity of that virtual - * sub-tree. - */ - public static class VirtualSubtreeRootView extends View { - - /** Paint object for drawing the virtual sub-tree */ - private final Paint mPaint = new Paint(); - - /** Temporary rectangle to minimize object creation. */ - private final Rect mTempRect = new Rect(); - - /** Handle to the system accessibility service. */ - private final AccessibilityManager mAccessibilityManager; - - /** The virtual children of this View. */ - private final List<VirtualView> mChildren = new ArrayList<VirtualView>(); - - /** The instance of the node provider for the virtual tree - lazily instantiated. */ - private AccessibilityNodeProvider mAccessibilityNodeProvider; - - /** The last hovered child used for event dispatching. */ - private VirtualView mLastHoveredChild; - - public VirtualSubtreeRootView(Context context, AttributeSet attrs) { - super(context, attrs); - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Service.ACCESSIBILITY_SERVICE); - createVirtualChildren(); - } - - /** - * {@inheritDoc} - */ - @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider() { - // Instantiate the provide only when requested. Since the system - // will call this method multiple times it is a good practice to - // cache the provider instance. - if (mAccessibilityNodeProvider == null) { - mAccessibilityNodeProvider = new VirtualDescendantsProvider(); - } - return mAccessibilityNodeProvider; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // This implementation assumes that the virtual children - // cannot overlap and are always visible. Do NOT use this - // code as a reference of how to implement hover event - // dispatch. Instead, refer to ViewGroup#dispatchHoverEvent. - boolean handled = false; - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - Rect childBounds = child.mBounds; - final int childCoordsX = (int) event.getX() + getScrollX(); - final int childCoordsY = (int) event.getY() + getScrollY(); - if (!childBounds.contains(childCoordsX, childCoordsY)) { - continue; - } - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: { - mLastHoveredChild = child; - handled |= onHoverVirtualView(child, event); - event.setAction(action); - } break; - case MotionEvent.ACTION_HOVER_MOVE: { - if (child == mLastHoveredChild) { - handled |= onHoverVirtualView(child, event); - event.setAction(action); - } else { - MotionEvent eventNoHistory = event.getHistorySize() > 0 - ? MotionEvent.obtainNoHistory(event) : event; - eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT); - onHoverVirtualView(mLastHoveredChild, eventNoHistory); - eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); - onHoverVirtualView(child, eventNoHistory); - mLastHoveredChild = child; - eventNoHistory.setAction(MotionEvent.ACTION_HOVER_MOVE); - handled |= onHoverVirtualView(child, eventNoHistory); - if (eventNoHistory != event) { - eventNoHistory.recycle(); - } else { - event.setAction(action); - } - } - } break; - case MotionEvent.ACTION_HOVER_EXIT: { - mLastHoveredChild = null; - handled |= onHoverVirtualView(child, event); - event.setAction(action); - } break; - } - } - if (!handled) { - handled |= onHoverEvent(event); - } - return handled; - } - - /** - * {@inheritDoc} - */ - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - // The virtual children are ordered horizontally next to - // each other and take the entire space of this View. - int offsetX = 0; - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - Rect childBounds = child.mBounds; - childBounds.set(offsetX, 0, offsetX + childBounds.width(), childBounds.height()); - offsetX += childBounds.width(); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // The virtual children are ordered horizontally next to - // each other and take the entire space of this View. - int width = 0; - int height = 0; - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - width += child.mBounds.width(); - height = Math.max(height, child.mBounds.height()); - } - setMeasuredDimension(width, height); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onDraw(Canvas canvas) { - // Draw the virtual children with the reusable Paint object - // and with the bounds and color which are child specific. - Rect drawingRect = mTempRect; - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - drawingRect.set(child.mBounds); - mPaint.setColor(child.mColor); - mPaint.setAlpha(child.mAlpha); - canvas.drawRect(drawingRect, mPaint); - } - } - - /** - * Creates the virtual children of this View. - */ - private void createVirtualChildren() { - // The virtual portion of the tree is one level deep. Note - // that implementations can use any way of representing and - // drawing virtual view. - VirtualView firstChild = new VirtualView(0, new Rect(0, 0, 150, 150), Color.RED, - "Virtual view 1"); - mChildren.add(firstChild); - VirtualView secondChild = new VirtualView(1, new Rect(0, 0, 150, 150), Color.GREEN, - "Virtual view 2"); - mChildren.add(secondChild); - VirtualView thirdChild = new VirtualView(2, new Rect(0, 0, 150, 150), Color.BLUE, - "Virtual view 3"); - mChildren.add(thirdChild); - } - - /** - * Set the selected state of a virtual view. - * - * @param virtualView The virtual view whose selected state to set. - * @param selected Whether the virtual view is selected. - */ - private void setVirtualViewSelected(VirtualView virtualView, boolean selected) { - virtualView.mAlpha = selected ? VirtualView.ALPHA_SELECTED : VirtualView.ALPHA_NOT_SELECTED; - } - - /** - * Handle a hover over a virtual view. - * - * @param virtualView The virtual view over which is hovered. - * @param event The event to dispatch. - * @return Whether the event was handled. - */ - private boolean onHoverVirtualView(VirtualView virtualView, MotionEvent event) { - // The implementation of hover event dispatch can be implemented - // in any way that is found suitable. However, each virtual View - // should fire a corresponding accessibility event whose source - // is that virtual view. Accessibility services get the event source - // as the entry point of the APIs for querying the window content. - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: { - sendAccessibilityEventForVirtualView(virtualView, - AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); - } break; - case MotionEvent.ACTION_HOVER_EXIT: { - sendAccessibilityEventForVirtualView(virtualView, - AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); - } break; - } - return true; - } - - /** - * Sends a properly initialized accessibility event for a virtual view.. - * - * @param virtualView The virtual view. - * @param eventType The type of the event to send. - */ - private void sendAccessibilityEventForVirtualView(VirtualView virtualView, int eventType) { - // If touch exploration, i.e. the user gets feedback while touching - // the screen, is enabled we fire accessibility events. - if (mAccessibilityManager.isTouchExplorationEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.setPackageName(getContext().getPackageName()); - event.setClassName(virtualView.getClass().getName()); - event.setSource(VirtualSubtreeRootView.this, virtualView.mId); - event.getText().add(virtualView.mText); - getParent().requestSendAccessibilityEvent(VirtualSubtreeRootView.this, event); - } - } - - /** - * Finds a virtual view given its id. - * - * @param id The virtual view id. - * @return The found virtual view. - */ - private VirtualView findVirtualViewById(int id) { - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - if (child.mId == id) { - return child; - } - } - return null; - } - - /** - * Represents a virtual View. - */ - private class VirtualView { - public static final int ALPHA_SELECTED = 255; - public static final int ALPHA_NOT_SELECTED = 127; - - public final int mId; - public final int mColor; - public final Rect mBounds; - public final String mText; - public int mAlpha; - - public VirtualView(int id, Rect bounds, int color, String text) { - mId = id; - mColor = color; - mBounds = bounds; - mText = text; - mAlpha = ALPHA_NOT_SELECTED; - } - } - - /** - * This is the provider that exposes the virtual View tree to accessibility - * services. From the perspective of an accessibility service the - * {@link AccessibilityNodeInfo}s it receives while exploring the sub-tree - * rooted at this View will be the same as the ones it received while - * exploring a View containing a sub-tree composed of real Views. - */ - private class VirtualDescendantsProvider extends AccessibilityNodeProvider { - - /** - * {@inheritDoc} - */ - @Override - public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { - AccessibilityNodeInfo info = null; - if (virtualViewId == View.NO_ID) { - // We are requested to create an AccessibilityNodeInfo describing - // this View, i.e. the root of the virtual sub-tree. Note that the - // host View has an AccessibilityNodeProvider which means that this - // provider is responsible for creating the node info for that root. - info = AccessibilityNodeInfo.obtain(VirtualSubtreeRootView.this); - onInitializeAccessibilityNodeInfo(info); - // Add the virtual children of the root View. - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - info.addChild(VirtualSubtreeRootView.this, child.mId); - } - } else { - // Find the view that corresponds to the given id. - VirtualView virtualView = findVirtualViewById(virtualViewId); - if (virtualView == null) { - return null; - } - // Obtain and initialize an AccessibilityNodeInfo with - // information about the virtual view. - info = AccessibilityNodeInfo.obtain(); - info.addAction(AccessibilityNodeInfo.ACTION_SELECT); - info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); - info.setPackageName(getContext().getPackageName()); - info.setClassName(virtualView.getClass().getName()); - info.setSource(VirtualSubtreeRootView.this, virtualViewId); - info.setBoundsInParent(virtualView.mBounds); - info.setParent(VirtualSubtreeRootView.this); - info.setText(virtualView.mText); - } - return info; - } - - /** - * {@inheritDoc} - */ - @Override - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched, - int virtualViewId) { - if (TextUtils.isEmpty(searched)) { - return Collections.emptyList(); - } - String searchedLowerCase = searched.toLowerCase(); - List<AccessibilityNodeInfo> result = null; - if (virtualViewId == View.NO_ID) { - // If the search is from the root, i.e. this View, go over the virtual - // children and look for ones that contain the searched string since - // this View does not contain text itself. - List<VirtualView> children = mChildren; - final int childCount = children.size(); - for (int i = 0; i < childCount; i++) { - VirtualView child = children.get(i); - String textToLowerCase = child.mText.toLowerCase(); - if (textToLowerCase.contains(searchedLowerCase)) { - if (result == null) { - result = new ArrayList<AccessibilityNodeInfo>(); - } - result.add(createAccessibilityNodeInfo(child.mId)); - } - } - } else { - // If the search is from a virtual view, find the view. Since the tree - // is one level deep we add a node info for the child to the result if - // the child contains the searched text. - VirtualView virtualView = findVirtualViewById(virtualViewId); - if (virtualView != null) { - String textToLowerCase = virtualView.mText.toLowerCase(); - if (textToLowerCase.contains(searchedLowerCase)) { - result = new ArrayList<AccessibilityNodeInfo>(); - result.add(createAccessibilityNodeInfo(virtualViewId)); - } - } - } - if (result == null) { - return Collections.emptyList(); - } - return result; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean performAction(int virtualViewId, int action, Bundle arguments) { - if (virtualViewId == View.NO_ID) { - // Perform the action on the host View. - switch (action) { - case AccessibilityNodeInfo.ACTION_SELECT: - if (!isSelected()) { - setSelected(true); - return isSelected(); - } - break; - case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: - if (isSelected()) { - setSelected(false); - return !isSelected(); - } - break; - } - } else { - // Find the view that corresponds to the given id. - VirtualView child = findVirtualViewById(virtualViewId); - if (child == null) { - return false; - } - // Perform the action on a virtual view. - switch (action) { - case AccessibilityNodeInfo.ACTION_SELECT: - setVirtualViewSelected(child, true); - invalidate(); - return true; - case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: - setVirtualViewSelected(child, false); - invalidate(); - return true; - } - } - return false; - } - } - } -} diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html index df54e966b..1324f8682 100644 --- a/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html +++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html @@ -21,12 +21,6 @@ xml files, and adding additional information to AccessibilityEvents using AccessibilityRecords. </dd> - <dt><a href="AccessibilityNodeProviderActivity.html">Accessibility Node Provider</a></dt> - <dd>Demonstrates how to develop an accessibility node provider which manages a virtual - View tree reported to accessibility services. The virtual subtree is rooted at a View - that draws complex content and reports itself as a tree of virtual views, thus conveying - its logical structure. - </dd> </dl> <dl> diff --git a/samples/ApiDemos/src/com/example/android/apis/animation/Transitions.java b/samples/ApiDemos/src/com/example/android/apis/animation/Transitions.java new file mode 100644 index 000000000..4878c5f19 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/animation/Transitions.java @@ -0,0 +1,91 @@ +/* + * 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.example.android.apis.animation; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.transition.Scene; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import com.example.android.apis.R; + +/** + * This application demonstrates some of the capabilities and uses of the + * {@link android.transition transitions} APIs. Scenes and a TransitionManager + * are loaded from resource files and transitions are run between those scenes + * as well as a dynamically-configured scene. + */ +public class Transitions extends Activity { + + Scene mScene1, mScene2, mScene3; + ViewGroup mSceneRoot; + TransitionManager mTransitionManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.transition); + + mSceneRoot = (ViewGroup) findViewById(R.id.sceneRoot); + + TransitionInflater inflater = TransitionInflater.from(this); + + // Note that this is not the only way to create a Scene object, but that + // loading them from layout resources cooperates with the + // TransitionManager that we are also loading from resources, and which + // uses the same layout resource files to determine the scenes to transition + // from/to. + mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.transition_scene1, this); + mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.transition_scene2, this); + mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.transition_scene3, this); + mTransitionManager = inflater.inflateTransitionManager(R.transition.transitions_mgr, + mSceneRoot); + } + + public void selectScene(View view) { + switch (view.getId()) { + case R.id.scene1: + mTransitionManager.transitionTo(mScene1); + break; + case R.id.scene2: + mTransitionManager.transitionTo(mScene2); + break; + case R.id.scene3: + mTransitionManager.transitionTo(mScene3); + break; + case R.id.scene4: + // scene4 is not an actual 'Scene', but rather a dynamic change in the UI, + // transitioned to using beginDelayedTransition() to tell the TransitionManager + // to get ready to run a transition at the next frame + TransitionManager.beginDelayedTransition(mSceneRoot); + setNewSize(R.id.view1, 150, 25); + setNewSize(R.id.view2, 150, 25); + setNewSize(R.id.view3, 150, 25); + setNewSize(R.id.view4, 150, 25); + break; + } + } + + private void setNewSize(int id, int width, int height) { + View view = findViewById(id); + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.width = width; + params.height = height; + view.setLayoutParams(params); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java index 5585c91a3..73d8db967 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java @@ -46,6 +46,8 @@ public class ActionBarDisplayOptions extends Activity findViewById(R.id.toggle_show_custom).setOnClickListener(this); findViewById(R.id.toggle_navigation).setOnClickListener(this); findViewById(R.id.cycle_custom_gravity).setOnClickListener(this); + findViewById(R.id.toggle_visibility).setOnClickListener(this); + findViewById(R.id.toggle_system_ui).setOnClickListener(this); mCustomView = getLayoutInflater().inflate(R.layout.action_bar_display_options_custom, null); // Configure several action bar elements that will be toggled by display options. @@ -93,20 +95,36 @@ public class ActionBarDisplayOptions extends Activity case R.id.cycle_custom_gravity: ActionBar.LayoutParams lp = (ActionBar.LayoutParams) mCustomView.getLayoutParams(); int newGravity = 0; - switch (lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.LEFT: + switch (lp.gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { + case Gravity.START: newGravity = Gravity.CENTER_HORIZONTAL; break; case Gravity.CENTER_HORIZONTAL: - newGravity = Gravity.RIGHT; + newGravity = Gravity.END; break; - case Gravity.RIGHT: - newGravity = Gravity.LEFT; + case Gravity.END: + newGravity = Gravity.START; break; } - lp.gravity = lp.gravity & ~Gravity.HORIZONTAL_GRAVITY_MASK | newGravity; + lp.gravity = lp.gravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK | newGravity; bar.setCustomView(mCustomView, lp); return; + case R.id.toggle_visibility: + if (bar.isShowing()) { + bar.hide(); + } else { + bar.show(); + } + return; + case R.id.toggle_system_ui: + if ((getWindow().getDecorView().getSystemUiVisibility() + & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) { + getWindow().getDecorView().setSystemUiVisibility(0); + } else { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_FULLSCREEN); + } + return; } int change = bar.getDisplayOptions() ^ flags; diff --git a/samples/ApiDemos/src/com/example/android/apis/app/AppUpdateSspReceiver.java b/samples/ApiDemos/src/com/example/android/apis/app/AppUpdateSspReceiver.java new file mode 100644 index 000000000..dfa265c64 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/AppUpdateSspReceiver.java @@ -0,0 +1,33 @@ +/* + * 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.example.android.apis.app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; + +/** + * Executed when a new version of the application is is installed. + */ +public class AppUpdateSspReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String msg = "Ssp update received: " + intent.getData(); + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java index 324b8ce6a..db501859e 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java @@ -68,6 +68,9 @@ public class DeviceAdminSample extends PreferenceActivity { // The following keys are used to find each preference item private static final String KEY_ENABLE_ADMIN = "key_enable_admin"; private static final String KEY_DISABLE_CAMERA = "key_disable_camera"; + private static final String KEY_DISABLE_KEYGUARD_WIDGETS = "key_disable_keyguard_widgets"; + private static final String KEY_DISABLE_KEYGUARD_SECURE_CAMERA + = "key_disable_keyguard_secure_camera"; private static final String KEY_CATEGORY_QUALITY = "key_category_quality"; private static final String KEY_SET_PASSWORD = "key_set_password"; @@ -245,6 +248,8 @@ public class DeviceAdminSample extends PreferenceActivity { // UI elements private CheckBoxPreference mEnableCheckbox; private CheckBoxPreference mDisableCameraCheckbox; + private CheckBoxPreference mDisableKeyguardWidgetsCheckbox; + private CheckBoxPreference mDisableKeyguardSecureCameraCheckbox; @Override public void onCreate(Bundle savedInstanceState) { @@ -254,6 +259,12 @@ public class DeviceAdminSample extends PreferenceActivity { mEnableCheckbox.setOnPreferenceChangeListener(this); mDisableCameraCheckbox = (CheckBoxPreference) findPreference(KEY_DISABLE_CAMERA); mDisableCameraCheckbox.setOnPreferenceChangeListener(this); + mDisableKeyguardWidgetsCheckbox = + (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_WIDGETS); + mDisableKeyguardWidgetsCheckbox.setOnPreferenceChangeListener(this); + mDisableKeyguardSecureCameraCheckbox = + (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_SECURE_CAMERA); + mDisableKeyguardSecureCameraCheckbox.setOnPreferenceChangeListener(this); } // At onResume time, reload UI with current values as required @@ -265,10 +276,20 @@ public class DeviceAdminSample extends PreferenceActivity { if (mAdminActive) { mDPM.setCameraDisabled(mDeviceAdminSample, mDisableCameraCheckbox.isChecked()); + mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, createKeyguardDisabledFlag()); reloadSummaries(); } } + int createKeyguardDisabledFlag() { + int flags = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; + flags |= mDisableKeyguardWidgetsCheckbox.isChecked() ? + DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL : 0; + flags |= mDisableKeyguardSecureCameraCheckbox.isChecked() ? + DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA : 0; + return flags; + } + @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (super.onPreferenceChange(preference, newValue)) { @@ -295,6 +316,10 @@ public class DeviceAdminSample extends PreferenceActivity { } else if (preference == mDisableCameraCheckbox) { mDPM.setCameraDisabled(mDeviceAdminSample, value); reloadSummaries(); + } else if (preference == mDisableKeyguardWidgetsCheckbox + || preference == mDisableKeyguardSecureCameraCheckbox) { + mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, createKeyguardDisabledFlag()); + reloadSummaries(); } return true; } @@ -305,11 +330,25 @@ public class DeviceAdminSample extends PreferenceActivity { String cameraSummary = getString(mDPM.getCameraDisabled(mDeviceAdminSample) ? R.string.camera_disabled : R.string.camera_enabled); mDisableCameraCheckbox.setSummary(cameraSummary); + + int disabled = mDPM.getKeyguardDisabledFeatures(mDeviceAdminSample); + + String keyguardWidgetSummary = getString( + (disabled & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0 ? + R.string.keyguard_widgets_disabled : R.string.keyguard_widgets_enabled); + mDisableKeyguardWidgetsCheckbox.setSummary(keyguardWidgetSummary); + + String keyguardSecureCameraSummary = getString( + (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 ? + R.string.keyguard_secure_camera_disabled : R.string.keyguard_secure_camera_enabled); + mDisableKeyguardSecureCameraCheckbox.setSummary(keyguardSecureCameraSummary); } /** Updates the device capabilities area (dis/enabling) as the admin is (de)activated */ private void enableDeviceCapabilitiesArea(boolean enabled) { mDisableCameraCheckbox.setEnabled(enabled); + mDisableKeyguardWidgetsCheckbox.setEnabled(enabled); + mDisableKeyguardSecureCameraCheckbox.setEnabled(enabled); } } diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DoNothing.java b/samples/ApiDemos/src/com/example/android/apis/app/DoNothing.java new file mode 100644 index 000000000..133a80260 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/DoNothing.java @@ -0,0 +1,27 @@ +/* + * 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.example.android.apis.app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class DoNothing extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java new file mode 100644 index 000000000..7023c4380 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Demonstrates a fragment that can be configured through both Bundle arguments + * and layout attributes. + */ +public class FragmentArgumentsFragment extends Fragment { + @Override public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + // First-time init; create fragment to embed in activity. + FragmentTransaction ft = getChildFragmentManager().beginTransaction(); + Fragment newFragment = FragmentArguments.MyFragment.newInstance("From Arguments 1"); + ft.add(R.id.created1, newFragment); + newFragment = FragmentArguments.MyFragment.newInstance("From Arguments 2"); + ft.add(R.id.created2, newFragment); + ft.commit(); + } + } + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_arguments_fragment, container, false); + return v; + } +//END_INCLUDE(create) +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java index 6bc73e038..420e67f35 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java @@ -29,6 +29,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ContextMenu.ContextMenuInfo; +import android.widget.Toast; /** * Demonstration of displaying a context menu from a fragment. @@ -65,10 +66,10 @@ public class FragmentContextMenu extends Activity { public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.a_item: - Log.i("ContextMenu", "Item 1a was chosen"); + Toast.makeText(getActivity(), "Item 1a was chosen", Toast.LENGTH_SHORT).show(); return true; case R.id.b_item: - Log.i("ContextMenu", "Item 1b was chosen"); + Toast.makeText(getActivity(), "Item 1b was chosen", Toast.LENGTH_SHORT).show(); return true; } return super.onContextItemSelected(item); diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java index 730f4d44b..572173fb2 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java @@ -150,7 +150,11 @@ public class FragmentLayout extends Activity { // Execute a transaction, replacing any existing fragment // with this one inside the frame. FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(R.id.details, details); + if (index == 0) { + ft.replace(R.id.details, details); + } else { + ft.replace(R.id.a_item, details); + } ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java new file mode 100644 index 000000000..6ac700355 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CheckBox; + +/** + * Demonstrates how fragments can participate in the options menu. + */ +public class FragmentMenuFragment extends Fragment { + Fragment mFragment1; + Fragment mFragment2; + CheckBox mCheckBox1; + CheckBox mCheckBox2; + + // Update fragment visibility when check boxes are changed. + final OnClickListener mClickListener = new OnClickListener() { + public void onClick(View v) { + updateFragmentVisibility(); + } + }; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_menu, container, false); + + // Make sure the two menu fragments are created. + FragmentManager fm = getChildFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + mFragment1 = fm.findFragmentByTag("f1"); + if (mFragment1 == null) { + mFragment1 = new FragmentMenu.MenuFragment(); + ft.add(mFragment1, "f1"); + } + mFragment2 = fm.findFragmentByTag("f2"); + if (mFragment2 == null) { + mFragment2 = new FragmentMenu.Menu2Fragment(); + ft.add(mFragment2, "f2"); + } + ft.commit(); + + // Watch check box clicks. + mCheckBox1 = (CheckBox)v.findViewById(R.id.menu1); + mCheckBox1.setOnClickListener(mClickListener); + mCheckBox2 = (CheckBox)v.findViewById(R.id.menu2); + mCheckBox2.setOnClickListener(mClickListener); + + // Make sure fragments start out with correct visibility. + updateFragmentVisibility(); + + return v; + } + + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + // Make sure fragments are updated after check box view state is restored. + updateFragmentVisibility(); + } + + // Update fragment visibility based on current check box state. + void updateFragmentVisibility() { + FragmentTransaction ft = getChildFragmentManager().beginTransaction(); + if (mCheckBox1.isChecked()) ft.show(mFragment1); + else ft.hide(mFragment1); + if (mCheckBox2.isChecked()) ft.show(mFragment2); + else ft.hide(mFragment2); + ft.commit(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java new file mode 100644 index 000000000..4331c2aec --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.apis.app; + +//BEGIN_INCLUDE(complete) +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.widget.Toast; + +/** + * This demonstrates the use of action bar tabs and how they interact + * with other action bar features. + */ +public class FragmentNestingTabs extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + FragmentManager.enableDebugLogging(true); + super.onCreate(savedInstanceState); + + final ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); + + bar.addTab(bar.newTab() + .setText("Menus") + .setTabListener(new TabListener<FragmentMenuFragment>( + this, "menus", FragmentMenuFragment.class))); + bar.addTab(bar.newTab() + .setText("Args") + .setTabListener(new TabListener<FragmentArgumentsFragment>( + this, "args", FragmentArgumentsFragment.class))); + bar.addTab(bar.newTab() + .setText("Stack") + .setTabListener(new TabListener<FragmentStackFragment>( + this, "stack", FragmentStackFragment.class))); + bar.addTab(bar.newTab() + .setText("Tabs") + .setTabListener(new TabListener<FragmentTabsFragment>( + this, "tabs", FragmentTabsFragment.class))); + + if (savedInstanceState != null) { + bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); + } + + public static class TabListener<T extends Fragment> implements ActionBar.TabListener { + private final Activity mActivity; + private final String mTag; + private final Class<T> mClass; + private final Bundle mArgs; + private Fragment mFragment; + + public TabListener(Activity activity, String tag, Class<T> clz) { + this(activity, tag, clz, null); + } + + public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) { + mActivity = activity; + mTag = tag; + mClass = clz; + mArgs = args; + + // Check to see if we already have a fragment for this tab, probably + // from a previously saved state. If so, deactivate it, because our + // initial state is that a tab isn't shown. + mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); + if (mFragment != null && !mFragment.isDetached()) { + FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); + ft.detach(mFragment); + ft.commit(); + } + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + if (mFragment == null) { + mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); + ft.add(android.R.id.content, mFragment, mTag); + } else { + ft.attach(mFragment); + } + } + + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + if (mFragment != null) { + ft.detach(mFragment); + } + } + + public void onTabReselected(Tab tab, FragmentTransaction ft) { + Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); + } + } +} +//END_INCLUDE(complete) diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java index 891eda486..242d67074 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java @@ -44,6 +44,12 @@ public class FragmentStack extends Activity { addFragmentToStack(); } }); + button = (Button)findViewById(R.id.delete_fragment); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + getFragmentManager().popBackStack(); + } + }); if (savedInstanceState == null) { // Do first time initialization -- add initial fragment. diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java new file mode 100644 index 000000000..d33af64a8 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.Button; + +public class FragmentStackFragment extends Fragment { + int mStackLevel = 1; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + // Do first time initialization -- add initial fragment. + Fragment newFragment = FragmentStack.CountingFragment.newInstance(mStackLevel); + FragmentTransaction ft = getChildFragmentManager().beginTransaction(); + ft.add(R.id.simple_fragment, newFragment).commit(); + } else { + mStackLevel = savedInstanceState.getInt("level"); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_stack, container, false); + + // Watch for button clicks. + Button button = (Button)v.findViewById(R.id.new_fragment); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + addFragmentToStack(); + } + }); + button = (Button)v.findViewById(R.id.delete_fragment); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + getChildFragmentManager().popBackStack(); + } + }); + + return v; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("level", mStackLevel); + } + + void addFragmentToStack() { + mStackLevel++; + + // Instantiate a new fragment. + Fragment newFragment = FragmentStack.CountingFragment.newInstance(mStackLevel); + + // Add the fragment to the activity, pushing this transaction + // on to the back stack. + FragmentTransaction ft = getChildFragmentManager().beginTransaction(); + ft.replace(R.id.simple_fragment, newFragment); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + ft.addToBackStack(null); + ft.commit(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java new file mode 100644 index 000000000..1d45e4dbe --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.apis.app; + +import java.util.ArrayList; + +import com.example.android.apis.R; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TabHost; + +/** + * Sample fragment that contains tabs of other fragments. + */ +public class FragmentTabsFragment extends Fragment { + TabManager mTabManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mTabManager = new TabManager(getActivity(), getChildFragmentManager(), + R.id.realtabcontent); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_tabs_fragment, container, false); + TabHost host = mTabManager.handleCreateView(v); + + mTabManager.addTab(host.newTabSpec("result").setIndicator("Result"), + FragmentReceiveResult.ReceiveResultFragment.class, null); + mTabManager.addTab(host.newTabSpec("contacts").setIndicator("Contacts"), + LoaderCursor.CursorLoaderListFragment.class, null); + mTabManager.addTab(host.newTabSpec("apps").setIndicator("Apps"), + LoaderCustom.AppListFragment.class, null); + mTabManager.addTab(host.newTabSpec("throttle").setIndicator("Throttle"), + LoaderThrottle.ThrottledLoaderListFragment.class, null); + + return v; + } + + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + mTabManager.handleViewStateRestored(savedInstanceState); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mTabManager.handleDestroyView(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mTabManager.handleSaveInstanceState(outState); + } + + /** + * This is a helper class that implements a generic mechanism for + * associating fragments with the tabs in a tab host. DO NOT USE THIS. + * If you want tabs in a fragment, use the support v13 library's + * FragmentTabHost class, which takes care of all of this for you (in + * a simpler way even). + */ + public static class TabManager implements TabHost.OnTabChangeListener { + private final Context mContext; + private final FragmentManager mManager; + private final int mContainerId; + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + private TabHost mTabHost; + private TabInfo mLastTab; + private boolean mInitialized; + private String mCurrentTabTag; + + static final class TabInfo { + private final String tag; + private final Class<?> clss; + private final Bundle args; + private Fragment fragment; + + TabInfo(String _tag, Class<?> _class, Bundle _args) { + tag = _tag; + clss = _class; + args = _args; + } + } + + static class DummyTabFactory implements TabHost.TabContentFactory { + private final Context mContext; + + public DummyTabFactory(Context context) { + mContext = context; + } + + @Override + public View createTabContent(String tag) { + View v = new View(mContext); + v.setMinimumWidth(0); + v.setMinimumHeight(0); + return v; + } + } + + public TabManager(Context context, FragmentManager manager, int containerId) { + mContext = context; + mManager = manager; + mContainerId = containerId; + } + + public TabHost handleCreateView(View root) { + if (mTabHost != null) { + throw new IllegalStateException("TabHost already set"); + } + mTabHost = (TabHost)root.findViewById(android.R.id.tabhost); + mTabHost.setup(); + mTabHost.setOnTabChangedListener(this); + return mTabHost; + } + + public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { + tabSpec.setContent(new DummyTabFactory(mContext)); + String tag = tabSpec.getTag(); + TabInfo info = new TabInfo(tag, clss, args); + mTabs.add(info); + mTabHost.addTab(tabSpec); + } + + public void handleViewStateRestored(Bundle savedInstanceState) { + if (savedInstanceState != null) { + mCurrentTabTag = savedInstanceState.getString("tab"); + } + mTabHost.setCurrentTabByTag(mCurrentTabTag); + + String currentTab = mTabHost.getCurrentTabTag(); + + // Go through all tabs and make sure their fragments match + // the correct state. + FragmentTransaction ft = null; + for (int i=0; i<mTabs.size(); i++) { + TabInfo tab = mTabs.get(i); + tab.fragment = mManager.findFragmentByTag(tab.tag); + if (tab.fragment != null && !tab.fragment.isDetached()) { + if (tab.tag.equals(currentTab)) { + // The fragment for this tab is already there and + // active, and it is what we really want to have + // as the current tab. Nothing to do. + mLastTab = tab; + } else { + // This fragment was restored in the active state, + // but is not the current tab. Deactivate it. + if (ft == null) { + ft = mManager.beginTransaction(); + } + ft.detach(tab.fragment); + } + } + } + + // We are now ready to go. Make sure we are switched to the + // correct tab. + mInitialized = true; + ft = doTabChanged(currentTab, ft); + if (ft != null) { + ft.commit(); + mManager.executePendingTransactions(); + } + } + + public void handleDestroyView() { + mCurrentTabTag = mTabHost.getCurrentTabTag(); + mTabHost = null; + mTabs.clear(); + mInitialized = false; + } + + public void handleSaveInstanceState(Bundle outState) { + outState.putString("tab", mTabHost != null + ? mTabHost.getCurrentTabTag() : mCurrentTabTag); + } + + @Override + public void onTabChanged(String tabId) { + if (!mInitialized) { + return; + } + FragmentTransaction ft = doTabChanged(tabId, null); + if (ft != null) { + ft.commit(); + } + } + + private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) { + TabInfo newTab = null; + for (int i=0; i<mTabs.size(); i++) { + TabInfo tab = mTabs.get(i); + if (tab.tag.equals(tabId)) { + newTab = tab; + } + } + if (newTab == null) { + throw new IllegalStateException("No tab known for tag " + tabId); + } + if (mLastTab != newTab) { + if (ft == null) { + ft = mManager.beginTransaction(); + } + if (mLastTab != null) { + if (mLastTab.fragment != null) { + ft.detach(mLastTab.fragment); + } + } + if (newTab != null) { + if (newTab.fragment == null) { + newTab.fragment = Fragment.instantiate(mContext, + newTab.clss.getName(), newTab.args); + ft.add(mContainerId, newTab.fragment, newTab.tag); + } else { + ft.attach(newTab.fragment); + } + } + + mLastTab = newTab; + } + return ft; + } + } +} +//END_INCLUDE(complete) diff --git a/samples/ApiDemos/src/com/example/android/apis/app/Intents.java b/samples/ApiDemos/src/com/example/android/apis/app/Intents.java index 8f02b8322..6207dd805 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/Intents.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/Intents.java @@ -31,17 +31,23 @@ public class Intents extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.intents); + } + + public void onGetMusic(View view) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("audio/*"); + startActivity(Intent.createChooser(intent, "Select music")); + } - // Watch for button clicks. - Button button = (Button)findViewById(R.id.get_music); - button.setOnClickListener(mGetMusicListener); + public void onGetImage(View view) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + startActivity(Intent.createChooser(intent, "Select image")); } - private OnClickListener mGetMusicListener = new OnClickListener() { - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("audio/*"); - startActivity(Intent.createChooser(intent, "Select music")); - } - }; + public void onGetStream(View view) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + startActivity(Intent.createChooser(intent, "Select stream")); + } } diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java index f30919146..4d2a4d057 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.app.FragmentManager; import android.app.ListFragment; import android.app.LoaderManager; +import android.content.Context; import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; @@ -34,6 +35,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ListView; import android.widget.SearchView; +import android.widget.SearchView.OnCloseListener; import android.widget.SimpleCursorAdapter; import android.widget.SearchView.OnQueryTextListener; @@ -58,11 +60,15 @@ public class LoaderCursor extends Activity { //BEGIN_INCLUDE(fragment_cursor) public static class CursorLoaderListFragment extends ListFragment - implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { + implements OnQueryTextListener, OnCloseListener, + LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; + // The SearchView for doing filtering. + SearchView mSearchView; + // If non-null, this is the current filter the user has provided. String mCurFilter; @@ -91,15 +97,31 @@ public class LoaderCursor extends Activity { getLoaderManager().initLoader(0, null, this); } + public static class MySearchView extends SearchView { + public MySearchView(Context context) { + super(context); + } + + // The normal SearchView doesn't clear its search text when + // collapsed, so we will do this for it. + @Override + public void onActionViewCollapsed() { + setQuery("", false); + super.onActionViewCollapsed(); + } + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - SearchView sv = new SearchView(getActivity()); - sv.setOnQueryTextListener(this); - item.setActionView(sv); + mSearchView = new MySearchView(getActivity()); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnCloseListener(this); + mSearchView.setIconifiedByDefault(true); + item.setActionView(mSearchView); } public boolean onQueryTextChange(String newText) { @@ -125,6 +147,14 @@ public class LoaderCursor extends Activity { return true; } + @Override + public boolean onClose() { + if (!TextUtils.isEmpty(mSearchView.getQuery())) { + mSearchView.setQuery(null, true); + } + return true; + } + @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java index 7c16fb359..ffcf890fb 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java @@ -17,6 +17,7 @@ package com.example.android.apis.app; import com.example.android.apis.R; +import com.example.android.apis.app.LoaderCursor.CursorLoaderListFragment.MySearchView; import java.io.File; import java.text.Collator; @@ -55,6 +56,7 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.SearchView; import android.widget.TextView; +import android.widget.SearchView.OnCloseListener; import android.widget.SearchView.OnQueryTextListener; /** @@ -397,11 +399,15 @@ public class LoaderCustom extends Activity { } public static class AppListFragment extends ListFragment - implements OnQueryTextListener, LoaderManager.LoaderCallbacks<List<AppEntry>> { + implements OnQueryTextListener, OnCloseListener, + LoaderManager.LoaderCallbacks<List<AppEntry>> { // This is the Adapter being used to display the list's data. AppListAdapter mAdapter; + // The SearchView for doing filtering. + SearchView mSearchView; + // If non-null, this is the current filter the user has provided. String mCurFilter; @@ -427,15 +433,31 @@ public class LoaderCustom extends Activity { getLoaderManager().initLoader(0, null, this); } + public static class MySearchView extends SearchView { + public MySearchView(Context context) { + super(context); + } + + // The normal SearchView doesn't clear its search text when + // collapsed, so we will do this for it. + @Override + public void onActionViewCollapsed() { + setQuery("", false); + super.onActionViewCollapsed(); + } + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - SearchView sv = new SearchView(getActivity()); - sv.setOnQueryTextListener(this); - item.setActionView(sv); + mSearchView = new MySearchView(getActivity()); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnCloseListener(this); + mSearchView.setIconifiedByDefault(true); + item.setActionView(mSearchView); } @Override public boolean onQueryTextChange(String newText) { @@ -451,6 +473,14 @@ public class LoaderCustom extends Activity { return true; } + @Override + public boolean onClose() { + if (!TextUtils.isEmpty(mSearchView.getQuery())) { + mSearchView.setQuery(null, true); + } + return true; + } + @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("LoaderCustom", "Item clicked: " + id); diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java index 521f6e5b2..7ddb737d0 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderRetained.java @@ -16,10 +16,13 @@ package com.example.android.apis.app; +import com.example.android.apis.app.LoaderCursor.CursorLoaderListFragment.MySearchView; + import android.app.Activity; import android.app.FragmentManager; import android.app.ListFragment; import android.app.LoaderManager; +import android.content.Context; import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; @@ -35,6 +38,7 @@ import android.view.View; import android.widget.ListView; import android.widget.SearchView; import android.widget.SimpleCursorAdapter; +import android.widget.SearchView.OnCloseListener; import android.widget.SearchView.OnQueryTextListener; /** @@ -58,11 +62,15 @@ public class LoaderRetained extends Activity { //BEGIN_INCLUDE(fragment_cursor) public static class CursorLoaderListFragment extends ListFragment - implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { + implements OnQueryTextListener, OnCloseListener, + LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; + // The SearchView for doing filtering. + SearchView mSearchView; + // If non-null, this is the current filter the user has provided. String mCurFilter; @@ -94,15 +102,31 @@ public class LoaderRetained extends Activity { getLoaderManager().initLoader(0, null, this); } + public static class MySearchView extends SearchView { + public MySearchView(Context context) { + super(context); + } + + // The normal SearchView doesn't clear its search text when + // collapsed, so we will do this for it. + @Override + public void onActionViewCollapsed() { + setQuery("", false); + super.onActionViewCollapsed(); + } + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - SearchView sv = new SearchView(getActivity()); - sv.setOnQueryTextListener(this); - item.setActionView(sv); + mSearchView = new MySearchView(getActivity()); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnCloseListener(this); + mSearchView.setIconifiedByDefault(true); + item.setActionView(mSearchView); } public boolean onQueryTextChange(String newText) { @@ -128,6 +152,14 @@ public class LoaderRetained extends Activity { return true; } + @Override + public boolean onClose() { + if (!TextUtils.isEmpty(mSearchView.getQuery())) { + mSearchView.setQuery(null, true); + } + return true; + } + @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); diff --git a/samples/ApiDemos/src/com/example/android/apis/app/OverscanActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/OverscanActivity.java new file mode 100644 index 000000000..38f4a0a64 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/OverscanActivity.java @@ -0,0 +1,39 @@ +/* + * 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.example.android.apis.app; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import android.widget.ImageView; +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; + +/** + * <h3>Overscan Activity</h3> + * + * <p>This demonstrates the how to write an activity that extends into the + * overscan region.</p> + */ +public class OverscanActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.overscan_activity); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java new file mode 100644 index 000000000..c626022be --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import com.example.android.apis.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.drawable.GradientDrawable; +import android.hardware.display.DisplayManager; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Parcelable.Creator; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +//BEGIN_INCLUDE(activity) +/** + * <h3>Presentation Activity</h3> + * + * <p> + * This demonstrates how to create an activity that shows some content + * on a secondary display using a {@link Presentation}. + * </p><p> + * The activity uses the {@link DisplayManager} API to enumerate displays. + * When the user selects a display, the activity opens a {@link Presentation} + * on that display. We show a different photograph in each presentation + * on a unique background along with a label describing the display. + * We also write information about displays and display-related events to + * the Android log which you can read using <code>adb logcat</code>. + * </p><p> + * You can try this out using an HDMI or Wifi display or by using the + * "Simulate secondary displays" feature in Development Settings to create a few + * simulated secondary displays. Each display will appear in the list along with a + * checkbox to show a presentation on that display. + * </p><p> + * See also the {@link PresentationWithMediaRouterActivity} sample which + * uses the media router to automatically select a secondary display + * on which to show content based on the currently selected route. + * </p> + */ +public class PresentationActivity extends Activity + implements OnCheckedChangeListener, OnClickListener { + private final String TAG = "PresentationActivity"; + + // Key for storing saved instance state. + private static final String PRESENTATION_KEY = "presentation"; + + // The content that we want to show on the presentation. + private static final int[] PHOTOS = new int[] { + R.drawable.frantic, + R.drawable.photo1, R.drawable.photo2, R.drawable.photo3, + R.drawable.photo4, R.drawable.photo5, R.drawable.photo6, + R.drawable.sample_4, + }; + + private DisplayManager mDisplayManager; + private DisplayListAdapter mDisplayListAdapter; + private CheckBox mShowAllDisplaysCheckbox; + private ListView mListView; + private int mNextImageNumber; + + // List of presentation contents indexed by displayId. + // This state persists so that we can restore the old presentation + // contents when the activity is paused or resumed. + private SparseArray<PresentationContents> mSavedPresentationContents; + + // List of all currently visible presentations indexed by display id. + private final SparseArray<DemoPresentation> mActivePresentations = + new SparseArray<DemoPresentation>(); + + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Restore saved instance state. + if (savedInstanceState != null) { + mSavedPresentationContents = + savedInstanceState.getSparseParcelableArray(PRESENTATION_KEY); + } else { + mSavedPresentationContents = new SparseArray<PresentationContents>(); + } + + // Get the display manager service. + mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE); + + // See assets/res/any/layout/presentation_activity.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.presentation_activity); + + // Set up checkbox to toggle between showing all displays or only presentation displays. + mShowAllDisplaysCheckbox = (CheckBox)findViewById(R.id.show_all_displays); + mShowAllDisplaysCheckbox.setOnCheckedChangeListener(this); + + // Set up the list of displays. + mDisplayListAdapter = new DisplayListAdapter(this); + mListView = (ListView)findViewById(R.id.display_list); + mListView.setAdapter(mDisplayListAdapter); + } + + @Override + protected void onResume() { + // Be sure to call the super class. + super.onResume(); + + // Update our list of displays on resume. + mDisplayListAdapter.updateContents(); + + // Restore presentations from before the activity was paused. + final int numDisplays = mDisplayListAdapter.getCount(); + for (int i = 0; i < numDisplays; i++) { + final Display display = mDisplayListAdapter.getItem(i); + final PresentationContents contents = + mSavedPresentationContents.get(display.getDisplayId()); + if (contents != null) { + showPresentation(display, contents); + } + } + mSavedPresentationContents.clear(); + + // Register to receive events from the display manager. + mDisplayManager.registerDisplayListener(mDisplayListener, null); + } + + @Override + protected void onPause() { + // Be sure to call the super class. + super.onPause(); + + // Unregister from the display manager. + mDisplayManager.unregisterDisplayListener(mDisplayListener); + + // Dismiss all of our presentations but remember their contents. + Log.d(TAG, "Activity is being paused. Dismissing all active presentation."); + for (int i = 0; i < mActivePresentations.size(); i++) { + DemoPresentation presentation = mActivePresentations.valueAt(i); + int displayId = mActivePresentations.keyAt(i); + mSavedPresentationContents.put(displayId, presentation.mContents); + presentation.dismiss(); + } + mActivePresentations.clear(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + // Be sure to call the super class. + super.onSaveInstanceState(outState); + outState.putSparseParcelableArray(PRESENTATION_KEY, mSavedPresentationContents); + } + + /** + * Shows a {@link Presentation} on the specified display. + */ + private void showPresentation(Display display, PresentationContents contents) { + final int displayId = display.getDisplayId(); + if (mActivePresentations.get(displayId) != null) { + return; + } + + Log.d(TAG, "Showing presentation photo #" + contents.photo + + " on display #" + displayId + "."); + + DemoPresentation presentation = new DemoPresentation(this, display, contents); + presentation.show(); + presentation.setOnDismissListener(mOnDismissListener); + mActivePresentations.put(displayId, presentation); + } + + /** + * Hides a {@link Presentation} on the specified display. + */ + private void hidePresentation(Display display) { + final int displayId = display.getDisplayId(); + DemoPresentation presentation = mActivePresentations.get(displayId); + if (presentation == null) { + return; + } + + Log.d(TAG, "Dismissing presentation on display #" + displayId + "."); + + presentation.dismiss(); + mActivePresentations.delete(displayId); + } + + private int getNextPhoto() { + final int photo = mNextImageNumber; + mNextImageNumber = (mNextImageNumber + 1) % PHOTOS.length; + return photo; + } + + /** + * Called when the show all displays checkbox is toggled or when + * an item in the list of displays is checked or unchecked. + */ + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView == mShowAllDisplaysCheckbox) { + // Show all displays checkbox was toggled. + mDisplayListAdapter.updateContents(); + } else { + // Display item checkbox was toggled. + final Display display = (Display)buttonView.getTag(); + if (isChecked) { + PresentationContents contents = new PresentationContents(getNextPhoto()); + showPresentation(display, contents); + } else { + hidePresentation(display); + } + } + } + + /** + * Called when the Info button next to a display is clicked to show information + * about the display. + */ + @Override + public void onClick(View v) { + Context context = v.getContext(); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + final Display display = (Display)v.getTag(); + Resources r = context.getResources(); + AlertDialog alert = builder + .setTitle(r.getString( + R.string.presentation_alert_info_text, display.getDisplayId())) + .setMessage(display.toString()) + .setNeutralButton(R.string.presentation_alert_dismiss_text, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .create(); + alert.show(); + } + + /** + * Listens for displays to be added, changed or removed. + * We use it to update the list and show a new {@link Presentation} when a + * display is connected. + * + * Note that we don't bother dismissing the {@link Presentation} when a + * display is removed, although we could. The presentation API takes care + * of doing that automatically for us. + */ + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + Log.d(TAG, "Display #" + displayId + " added."); + mDisplayListAdapter.updateContents(); + } + + @Override + public void onDisplayChanged(int displayId) { + Log.d(TAG, "Display #" + displayId + " changed."); + mDisplayListAdapter.updateContents(); + } + + @Override + public void onDisplayRemoved(int displayId) { + Log.d(TAG, "Display #" + displayId + " removed."); + mDisplayListAdapter.updateContents(); + } + }; + + /** + * Listens for when presentations are dismissed. + */ + private final DialogInterface.OnDismissListener mOnDismissListener = + new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + DemoPresentation presentation = (DemoPresentation)dialog; + int displayId = presentation.getDisplay().getDisplayId(); + Log.d(TAG, "Presentation on display #" + displayId + " was dismissed."); + mActivePresentations.delete(displayId); + mDisplayListAdapter.notifyDataSetChanged(); + } + }; + + /** + * List adapter. + * Shows information about all displays. + */ + private final class DisplayListAdapter extends ArrayAdapter<Display> { + final Context mContext; + + public DisplayListAdapter(Context context) { + super(context, R.layout.presentation_list_item); + mContext = context; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View v; + if (convertView == null) { + v = ((Activity) mContext).getLayoutInflater().inflate( + R.layout.presentation_list_item, null); + } else { + v = convertView; + } + + final Display display = getItem(position); + final int displayId = display.getDisplayId(); + + CheckBox cb = (CheckBox)v.findViewById(R.id.checkbox_presentation); + cb.setTag(display); + cb.setOnCheckedChangeListener(PresentationActivity.this); + cb.setChecked(mActivePresentations.indexOfKey(displayId) >= 0 + || mSavedPresentationContents.indexOfKey(displayId) >= 0); + + TextView tv = (TextView)v.findViewById(R.id.display_id); + tv.setText(v.getContext().getResources().getString( + R.string.presentation_display_id_text, displayId, display.getName())); + + Button b = (Button)v.findViewById(R.id.info); + b.setTag(display); + b.setOnClickListener(PresentationActivity.this); + + return v; + } + + /** + * Update the contents of the display list adapter to show + * information about all current displays. + */ + public void updateContents() { + clear(); + + String displayCategory = getDisplayCategory(); + Display[] displays = mDisplayManager.getDisplays(displayCategory); + addAll(displays); + + Log.d(TAG, "There are currently " + displays.length + " displays connected."); + for (Display display : displays) { + Log.d(TAG, " " + display); + } + } + + private String getDisplayCategory() { + return mShowAllDisplaysCheckbox.isChecked() ? null : + DisplayManager.DISPLAY_CATEGORY_PRESENTATION; + } + } + + /** + * The presentation to show on the secondary display. + * + * Note that the presentation display may have different metrics from the display on which + * the main activity is showing so we must be careful to use the presentation's + * own {@link Context} whenever we load resources. + */ + private final class DemoPresentation extends Presentation { + + final PresentationContents mContents; + + public DemoPresentation(Context context, Display display, PresentationContents contents) { + super(context, display); + mContents = contents; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Get the resources for the context of the presentation. + // Notice that we are getting the resources from the context of the presentation. + Resources r = getContext().getResources(); + + // Inflate the layout. + setContentView(R.layout.presentation_content); + + final Display display = getDisplay(); + final int displayId = display.getDisplayId(); + final int photo = mContents.photo; + + // Show a caption to describe what's going on. + TextView text = (TextView)findViewById(R.id.text); + text.setText(r.getString(R.string.presentation_photo_text, + photo, displayId, display.getName())); + + // Show a n image for visual interest. + ImageView image = (ImageView)findViewById(R.id.image); + image.setImageDrawable(r.getDrawable(PHOTOS[photo])); + + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setGradientType(GradientDrawable.RADIAL_GRADIENT); + + // Set the background to a random gradient. + Point p = new Point(); + getDisplay().getSize(p); + drawable.setGradientRadius(Math.max(p.x, p.y) / 2); + drawable.setColors(mContents.colors); + findViewById(android.R.id.content).setBackground(drawable); + } + } + + /** + * Information about the content we want to show in a presentation. + */ + private final static class PresentationContents implements Parcelable { + final int photo; + final int[] colors; + + public static final Creator<PresentationContents> CREATOR = + new Creator<PresentationContents>() { + @Override + public PresentationContents createFromParcel(Parcel in) { + return new PresentationContents(in); + } + + @Override + public PresentationContents[] newArray(int size) { + return new PresentationContents[size]; + } + }; + + public PresentationContents(int photo) { + this.photo = photo; + colors = new int[] { + ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000, + ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000 }; + } + + private PresentationContents(Parcel in) { + photo = in.readInt(); + colors = new int[] { in.readInt(), in.readInt() }; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(photo); + dest.writeInt(colors[0]); + dest.writeInt(colors[1]); + } + } +} +//END_INCLUDE(activity) diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java new file mode 100644 index 000000000..5ba476ea8 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; +import com.example.android.apis.graphics.CubeRenderer; + +import android.app.Activity; +import android.app.MediaRouteActionProvider; +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.Display; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +//BEGIN_INCLUDE(activity) +/** + * <h3>Presentation Activity</h3> + * + * <p> + * This demonstrates how to create an activity that shows some content + * on a secondary display using a {@link Presentation}. + * </p><p> + * The activity uses the {@link MediaRouter} API to automatically detect when + * a presentation display is available and to allow the user to control the + * media routes using a menu item. When a presentation display is available, + * we stop showing content in the main activity and instead open up a + * {@link Presentation} on the preferred presentation display. When a presentation + * display is removed, we revert to showing content in the main activity. + * We also write information about displays and display-related events to + * the Android log which you can read using <code>adb logcat</code>. + * </p><p> + * You can try this out using an HDMI or Wifi display or by using the + * "Simulate secondary displays" feature in Development Settings to create a few + * simulated secondary displays. Each display will appear in the list along with a + * checkbox to show a presentation on that display. + * </p><p> + * See also the {@link PresentationActivity} sample which + * uses the low-level display manager to enumerate displays and to show multiple + * simultaneous presentations on different displays. + * </p> + */ +public class PresentationWithMediaRouterActivity extends Activity { + private final String TAG = "PresentationWithMediaRouterActivity"; + + private MediaRouter mMediaRouter; + private DemoPresentation mPresentation; + private GLSurfaceView mSurfaceView; + private TextView mInfoTextView; + private boolean mPaused; + + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Get the media router service. + mMediaRouter = (MediaRouter)getSystemService(Context.MEDIA_ROUTER_SERVICE); + + // See assets/res/any/layout/presentation_with_media_router_activity.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.presentation_with_media_router_activity); + + // Set up the surface view for visual interest. + mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view); + mSurfaceView.setRenderer(new CubeRenderer(false)); + + // Get a text view where we will show information about what's happening. + mInfoTextView = (TextView)findViewById(R.id.info); + } + + @Override + protected void onResume() { + // Be sure to call the super class. + super.onResume(); + + // Listen for changes to media routes. + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback); + + // Update the presentation based on the currently selected route. + mPaused = false; + updatePresentation(); + } + + @Override + protected void onPause() { + // Be sure to call the super class. + super.onPause(); + + // Stop listening for changes to media routes. + mMediaRouter.removeCallback(mMediaRouterCallback); + + // Pause rendering. + mPaused = true; + updateContents(); + } + + @Override + protected void onStop() { + // Be sure to call the super class. + super.onStop(); + + // Dismiss the presentation when the activity is not visible. + if (mPresentation != null) { + Log.i(TAG, "Dismissing presentation because the activity is no longer visible."); + mPresentation.dismiss(); + mPresentation = null; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Be sure to call the super class. + super.onCreateOptionsMenu(menu); + + // Inflate the menu and configure the media router action provider. + getMenuInflater().inflate(R.menu.presentation_with_media_router_menu, menu); + + MenuItem mediaRouteMenuItem = menu.findItem(R.id.menu_media_route); + MediaRouteActionProvider mediaRouteActionProvider = + (MediaRouteActionProvider)mediaRouteMenuItem.getActionProvider(); + mediaRouteActionProvider.setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_VIDEO); + + // Return true to show the menu. + return true; + } + + private void updatePresentation() { + // Get the current route and its presentation display. + MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_LIVE_VIDEO); + Display presentationDisplay = route != null ? route.getPresentationDisplay() : null; + + // Dismiss the current presentation if the display has changed. + if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { + Log.i(TAG, "Dismissing presentation because the current route no longer " + + "has a presentation display."); + mPresentation.dismiss(); + mPresentation = null; + } + + // Show a new presentation if needed. + if (mPresentation == null && presentationDisplay != null) { + Log.i(TAG, "Showing presentation on display: " + presentationDisplay); + mPresentation = new DemoPresentation(this, presentationDisplay); + mPresentation.setOnDismissListener(mOnDismissListener); + try { + mPresentation.show(); + } catch (WindowManager.InvalidDisplayException ex) { + Log.w(TAG, "Couldn't show presentation! Display was removed in " + + "the meantime.", ex); + mPresentation = null; + } + } + + // Update the contents playing in this activity. + updateContents(); + } + + private void updateContents() { + // Show either the content in the main activity or the content in the presentation + // along with some descriptive text about what is happening. + if (mPresentation != null) { + mInfoTextView.setText(getResources().getString( + R.string.presentation_with_media_router_now_playing_remotely, + mPresentation.getDisplay().getName())); + mSurfaceView.setVisibility(View.INVISIBLE); + mSurfaceView.onPause(); + if (mPaused) { + mPresentation.getSurfaceView().onPause(); + } else { + mPresentation.getSurfaceView().onResume(); + } + } else { + mInfoTextView.setText(getResources().getString( + R.string.presentation_with_media_router_now_playing_locally, + getWindowManager().getDefaultDisplay().getName())); + mSurfaceView.setVisibility(View.VISIBLE); + if (mPaused) { + mSurfaceView.onPause(); + } else { + mSurfaceView.onResume(); + } + } + } + + private final MediaRouter.SimpleCallback mMediaRouterCallback = + new MediaRouter.SimpleCallback() { + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); + updatePresentation(); + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); + updatePresentation(); + } + + @Override + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); + updatePresentation(); + } + }; + + /** + * Listens for when presentations are dismissed. + */ + private final DialogInterface.OnDismissListener mOnDismissListener = + new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (dialog == mPresentation) { + Log.i(TAG, "Presentation was dismissed."); + mPresentation = null; + updateContents(); + } + } + }; + + /** + * The presentation to show on the secondary display. + * <p> + * Note that this display may have different metrics from the display on which + * the main activity is showing so we must be careful to use the presentation's + * own {@link Context} whenever we load resources. + * </p> + */ + private final static class DemoPresentation extends Presentation { + private GLSurfaceView mSurfaceView; + + public DemoPresentation(Context context, Display display) { + super(context, display); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Get the resources for the context of the presentation. + // Notice that we are getting the resources from the context of the presentation. + Resources r = getContext().getResources(); + + // Inflate the layout. + setContentView(R.layout.presentation_with_media_router_content); + + // Set up the surface view for visual interest. + mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view); + mSurfaceView.setRenderer(new CubeRenderer(false)); + } + + public GLSurfaceView getSurfaceView() { + return mSurfaceView; + } + } +} +//END_INCLUDE(activity) diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PrintBitmap.java b/samples/ApiDemos/src/com/example/android/apis/app/PrintBitmap.java new file mode 100644 index 000000000..762478a99 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PrintBitmap.java @@ -0,0 +1,82 @@ +package com.example.android.apis.app; +/* + * 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. + */ + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.print.PrintManager; +import android.support.v4.print.PrintHelper; +import android.view.Menu; +import android.view.MenuItem; +import android.webkit.WebView; +import android.widget.ImageView; + +import com.example.android.apis.R; + +/** + * This class demonstrates how to implement bitmap printing. + * <p> + * This activity shows an image and offers a print option in the overflow + * menu. When the user chooses to print a helper class from the support + * library is used to print the image. + * </p> + * + * @see PrintManager + * @see WebView + */ +public class PrintBitmap extends Activity { + + private ImageView mImageView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.print_bitmap); + mImageView = (ImageView) findViewById(R.id.image); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.print_custom_content, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_print) { + print(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void print() { + // Get the print manager. + PrintHelper printHelper = new PrintHelper(this); + + // Set the desired scale mode. + printHelper.setScaleMode(PrintHelper.SCALE_MODE_FIT); + + // Get the bitmap for the ImageView's drawable. + Bitmap bitmap = ((BitmapDrawable) mImageView.getDrawable()).getBitmap(); + + // Print the bitmap. + printHelper.printBitmap("Print Bitmap", bitmap); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PrintCustomContent.java b/samples/ApiDemos/src/com/example/android/apis/app/PrintCustomContent.java new file mode 100644 index 000000000..7a7e7ed80 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PrintCustomContent.java @@ -0,0 +1,562 @@ +/* + * 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.example.android.apis.app; + +import android.app.ListActivity; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.pdf.PdfDocument.Page; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.CancellationSignal.OnCancelListener; +import android.os.ParcelFileDescriptor; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintDocumentInfo; +import android.print.PrintManager; +import android.print.pdf.PrintedPdfDocument; +import android.util.SparseIntArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.example.android.apis.R; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This class demonstrates how to implement custom printing support. + * <p> + * This activity shows the list of the MotoGP champions by year and + * brand. The print option in the overflow menu allows the user to + * print the content. The list list of items is laid out to such that + * it fits the options selected by the user from the UI such as page + * size. Hence, for different page sizes the printed content will have + * different page count. + * </p> + * <p> + * This sample demonstrates how to completely implement a {@link + * PrintDocumentAdapter} in which: + * <ul> + * <li>Layout based on the selected print options is performed.</li> + * <li>Layout work is performed only if print options change would change the content.</li> + * <li>Layout result is properly reported.</li> + * <li>Only requested pages are written.</li> + * <li>Write result is properly reported.</li> + * <li>Both Layout and write respond to cancellation.</li> + * <li>Layout and render of views is demonstrated.</li> + * </ul> + * </p> + * + * @see PrintManager + * @see PrintDocumentAdapter + */ +public class PrintCustomContent extends ListActivity { + + private static final int MILS_IN_INCH = 1000; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setListAdapter(new MotoGpStatAdapter(loadMotoGpStats(), + getLayoutInflater())); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.print_custom_content, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_print) { + print(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void print() { + PrintManager printManager = (PrintManager) getSystemService( + Context.PRINT_SERVICE); + + printManager.print("MotoGP stats", + new PrintDocumentAdapter() { + private int mRenderPageWidth; + private int mRenderPageHeight; + + private PrintAttributes mPrintAttributes; + private PrintDocumentInfo mDocumentInfo; + private Context mPrintContext; + + @Override + public void onLayout(final PrintAttributes oldAttributes, + final PrintAttributes newAttributes, + final CancellationSignal cancellationSignal, + final LayoutResultCallback callback, + final Bundle metadata) { + + // If we are already cancelled, don't do any work. + if (cancellationSignal.isCanceled()) { + callback.onLayoutCancelled(); + return; + } + + // Now we determined if the print attributes changed in a way that + // would change the layout and if so we will do a layout pass. + boolean layoutNeeded = false; + + final int density = Math.max(newAttributes.getResolution().getHorizontalDpi(), + newAttributes.getResolution().getVerticalDpi()); + + // Note that we are using the PrintedPdfDocument class which creates + // a PDF generating canvas whose size is in points (1/72") not screen + // pixels. Hence, this canvas is pretty small compared to the screen. + // The recommended way is to layout the content in the desired size, + // in this case as large as the printer can do, and set a translation + // to the PDF canvas to shrink in. Note that PDF is a vector format + // and you will not lose data during the transformation. + + // The content width is equal to the page width minus the margins times + // the horizontal printer density. This way we get the maximal number + // of pixels the printer can put horizontally. + final int marginLeft = (int) (density * (float) newAttributes.getMinMargins() + .getLeftMils() / MILS_IN_INCH); + final int marginRight = (int) (density * (float) newAttributes.getMinMargins() + .getRightMils() / MILS_IN_INCH); + final int contentWidth = (int) (density * (float) newAttributes.getMediaSize() + .getWidthMils() / MILS_IN_INCH) - marginLeft - marginRight; + if (mRenderPageWidth != contentWidth) { + mRenderPageWidth = contentWidth; + layoutNeeded = true; + } + + // The content height is equal to the page height minus the margins times + // the vertical printer resolution. This way we get the maximal number + // of pixels the printer can put vertically. + final int marginTop = (int) (density * (float) newAttributes.getMinMargins() + .getTopMils() / MILS_IN_INCH); + final int marginBottom = (int) (density * (float) newAttributes.getMinMargins() + .getBottomMils() / MILS_IN_INCH); + final int contentHeight = (int) (density * (float) newAttributes.getMediaSize() + .getHeightMils() / MILS_IN_INCH) - marginTop - marginBottom; + if (mRenderPageHeight != contentHeight) { + mRenderPageHeight = contentHeight; + layoutNeeded = true; + } + + // Create a context for resources at printer density. We will + // be inflating views to render them and would like them to use + // resources for a density the printer supports. + if (mPrintContext == null || mPrintContext.getResources() + .getConfiguration().densityDpi != density) { + Configuration configuration = new Configuration(); + configuration.densityDpi = density; + mPrintContext = createConfigurationContext( + configuration); + mPrintContext.setTheme(android.R.style.Theme_Holo_Light); + } + + // If no layout is needed that we did a layout at least once and + // the document info is not null, also the second argument is false + // to notify the system that the content did not change. This is + // important as if the system has some pages and the content didn't + // change the system will ask, the application to write them again. + if (!layoutNeeded) { + callback.onLayoutFinished(mDocumentInfo, false); + return; + } + + // For demonstration purposes we will do the layout off the main + // thread but for small content sizes like this one it is OK to do + // that on the main thread. + + // Store the data as we will layout off the main thread. + final List<MotoGpStatItem> items = ((MotoGpStatAdapter) + getListAdapter()).cloneItems(); + + new AsyncTask<Void, Void, PrintDocumentInfo>() { + @Override + protected void onPreExecute() { + // First register for cancellation requests. + cancellationSignal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + cancel(true); + } + }); + // Stash the attributes as we will need them for rendering. + mPrintAttributes = newAttributes; + } + + @Override + protected PrintDocumentInfo doInBackground(Void... params) { + try { + // Create an adapter with the stats and an inflater + // to load resources for the printer density. + MotoGpStatAdapter adapter = new MotoGpStatAdapter(items, + (LayoutInflater) mPrintContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE)); + + int currentPage = 0; + int pageContentHeight = 0; + int viewType = -1; + View view = null; + LinearLayout dummyParent = new LinearLayout(mPrintContext); + dummyParent.setOrientation(LinearLayout.VERTICAL); + + final int itemCount = adapter.getCount(); + for (int i = 0; i < itemCount; i++) { + // Be nice and respond to cancellation. + if (isCancelled()) { + return null; + } + + // Get the next view. + final int nextViewType = adapter.getItemViewType(i); + if (viewType == nextViewType) { + view = adapter.getView(i, view, dummyParent); + } else { + view = adapter.getView(i, null, dummyParent); + } + viewType = nextViewType; + + // Measure the next view + measureView(view); + + // Add the height but if the view crosses the page + // boundary we will put it to the next page. + pageContentHeight += view.getMeasuredHeight(); + if (pageContentHeight > mRenderPageHeight) { + pageContentHeight = view.getMeasuredHeight(); + currentPage++; + } + } + + // Create a document info describing the result. + PrintDocumentInfo info = new PrintDocumentInfo + .Builder("MotoGP_stats.pdf") + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) + .setPageCount(currentPage + 1) + .build(); + + // We completed the layout as a result of print attributes + // change. Hence, if we are here the content changed for + // sure which is why we pass true as the second argument. + callback.onLayoutFinished(info, true); + return info; + } catch (Exception e) { + // An unexpected error, report that we failed and + // one may pass in a human readable localized text + // for what the error is if known. + callback.onLayoutFailed(null); + throw new RuntimeException(e); + } + } + + @Override + protected void onPostExecute(PrintDocumentInfo result) { + // Update the cached info to send it over if the next + // layout pass does not result in a content change. + mDocumentInfo = result; + } + + @Override + protected void onCancelled(PrintDocumentInfo result) { + // Task was cancelled, report that. + callback.onLayoutCancelled(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + @Override + public void onWrite(final PageRange[] pages, + final ParcelFileDescriptor destination, + final CancellationSignal cancellationSignal, + final WriteResultCallback callback) { + + // If we are already cancelled, don't do any work. + if (cancellationSignal.isCanceled()) { + callback.onWriteCancelled(); + return; + } + + // Store the data as we will layout off the main thread. + final List<MotoGpStatItem> items = ((MotoGpStatAdapter) + getListAdapter()).cloneItems(); + + new AsyncTask<Void, Void, Void>() { + private final SparseIntArray mWrittenPages = new SparseIntArray(); + private final PrintedPdfDocument mPdfDocument = new PrintedPdfDocument( + PrintCustomContent.this, mPrintAttributes); + + @Override + protected void onPreExecute() { + // First register for cancellation requests. + cancellationSignal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + cancel(true); + } + }); + } + + @Override + protected Void doInBackground(Void... params) { + // Go over all the pages and write only the requested ones. + // Create an adapter with the stats and an inflater + // to load resources for the printer density. + MotoGpStatAdapter adapter = new MotoGpStatAdapter(items, + (LayoutInflater) mPrintContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE)); + + int currentPage = -1; + int pageContentHeight = 0; + int viewType = -1; + View view = null; + Page page = null; + LinearLayout dummyParent = new LinearLayout(mPrintContext); + dummyParent.setOrientation(LinearLayout.VERTICAL); + + // The content is laid out and rendered in screen pixels with + // the width and height of the paper size times the print + // density but the PDF canvas size is in points which are 1/72", + // so we will scale down the content. + final float scale = Math.min( + (float) mPdfDocument.getPageContentRect().width() + / mRenderPageWidth, + (float) mPdfDocument.getPageContentRect().height() + / mRenderPageHeight); + + final int itemCount = adapter.getCount(); + for (int i = 0; i < itemCount; i++) { + // Be nice and respond to cancellation. + if (isCancelled()) { + return null; + } + + // Get the next view. + final int nextViewType = adapter.getItemViewType(i); + if (viewType == nextViewType) { + view = adapter.getView(i, view, dummyParent); + } else { + view = adapter.getView(i, null, dummyParent); + } + viewType = nextViewType; + + // Measure the next view + measureView(view); + + // Add the height but if the view crosses the page + // boundary we will put it to the next one. + pageContentHeight += view.getMeasuredHeight(); + if (currentPage < 0 || pageContentHeight > mRenderPageHeight) { + pageContentHeight = view.getMeasuredHeight(); + currentPage++; + // Done with the current page - finish it. + if (page != null) { + mPdfDocument.finishPage(page); + } + // If the page is requested, render it. + if (containsPage(pages, currentPage)) { + page = mPdfDocument.startPage(currentPage); + page.getCanvas().scale(scale, scale); + // Keep track which pages are written. + mWrittenPages.append(mWrittenPages.size(), currentPage); + } else { + page = null; + } + } + + // If the current view is on a requested page, render it. + if (page != null) { + // Layout an render the content. + view.layout(0, 0, view.getMeasuredWidth(), + view.getMeasuredHeight()); + view.draw(page.getCanvas()); + // Move the canvas for the next view. + page.getCanvas().translate(0, view.getHeight()); + } + } + + // Done with the last page. + if (page != null) { + mPdfDocument.finishPage(page); + } + + // Write the data and return success or failure. + try { + mPdfDocument.writeTo(new FileOutputStream( + destination.getFileDescriptor())); + // Compute which page ranges were written based on + // the bookkeeping we maintained. + PageRange[] pageRanges = computeWrittenPageRanges(mWrittenPages); + callback.onWriteFinished(pageRanges); + } catch (IOException ioe) { + callback.onWriteFailed(null); + } finally { + mPdfDocument.close(); + } + + return null; + } + + @Override + protected void onCancelled(Void result) { + // Task was cancelled, report that. + callback.onWriteCancelled(); + mPdfDocument.close(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + private void measureView(View view) { + final int widthMeasureSpec = ViewGroup.getChildMeasureSpec( + MeasureSpec.makeMeasureSpec(mRenderPageWidth, + MeasureSpec.EXACTLY), 0, view.getLayoutParams().width); + final int heightMeasureSpec = ViewGroup.getChildMeasureSpec( + MeasureSpec.makeMeasureSpec(mRenderPageHeight, + MeasureSpec.EXACTLY), 0, view.getLayoutParams().height); + view.measure(widthMeasureSpec, heightMeasureSpec); + } + + private PageRange[] computeWrittenPageRanges(SparseIntArray writtenPages) { + List<PageRange> pageRanges = new ArrayList<PageRange>(); + + int start = -1; + int end = -1; + final int writtenPageCount = writtenPages.size(); + for (int i = 0; i < writtenPageCount; i++) { + if (start < 0) { + start = writtenPages.valueAt(i); + } + int oldEnd = end = start; + while (i < writtenPageCount && (end - oldEnd) <= 1) { + oldEnd = end; + end = writtenPages.valueAt(i); + i++; + } + PageRange pageRange = new PageRange(start, end); + pageRanges.add(pageRange); + start = end = -1; + } + + PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; + pageRanges.toArray(pageRangesArray); + return pageRangesArray; + } + + private boolean containsPage(PageRange[] pageRanges, int page) { + final int pageRangeCount = pageRanges.length; + for (int i = 0; i < pageRangeCount; i++) { + if (pageRanges[i].getStart() <= page + && pageRanges[i].getEnd() >= page) { + return true; + } + } + return false; + } + }, null); + } + + private List<MotoGpStatItem> loadMotoGpStats() { + String[] years = getResources().getStringArray(R.array.motogp_years); + String[] champions = getResources().getStringArray(R.array.motogp_champions); + String[] constructors = getResources().getStringArray(R.array.motogp_constructors); + + List<MotoGpStatItem> items = new ArrayList<MotoGpStatItem>(); + + final int itemCount = years.length; + for (int i = 0; i < itemCount; i++) { + MotoGpStatItem item = new MotoGpStatItem(); + item.year = years[i]; + item.champion = champions[i]; + item.constructor = constructors[i]; + items.add(item); + } + + return items; + } + + private static final class MotoGpStatItem { + String year; + String champion; + String constructor; + } + + private class MotoGpStatAdapter extends BaseAdapter { + private final List<MotoGpStatItem> mItems; + private final LayoutInflater mInflater; + + public MotoGpStatAdapter(List<MotoGpStatItem> items, LayoutInflater inflater) { + mItems = items; + mInflater = inflater; + } + + public List<MotoGpStatItem> cloneItems() { + return new ArrayList<MotoGpStatItem>(mItems); + } + + @Override + public int getCount() { + return mItems.size(); + } + + @Override + public Object getItem(int position) { + return mItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.motogp_stat_item, parent, false); + } + + MotoGpStatItem item = (MotoGpStatItem) getItem(position); + + TextView yearView = (TextView) convertView.findViewById(R.id.year); + yearView.setText(item.year); + + TextView championView = (TextView) convertView.findViewById(R.id.champion); + championView.setText(item.champion); + + TextView constructorView = (TextView) convertView.findViewById(R.id.constructor); + constructorView.setText(item.constructor); + + return convertView; + } + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlFromScreen.java b/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlFromScreen.java new file mode 100644 index 000000000..55c98ffc9 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlFromScreen.java @@ -0,0 +1,93 @@ +/* + * 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.example.android.apis.app; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.print.PrintManager; +import android.view.Menu; +import android.view.MenuItem; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.example.android.apis.R; + +/** + * This class demonstrates how to implement HTML content printing + * from a {@link WebView} which is shown on the screen. + * <p> + * This activity shows a simple HTML content in a {@link WebView} + * and allows the user to print that content via an action in the + * action bar. The shown {@link WebView} is doing the printing. + * </p> + * + * @see PrintManager + * @see WebView + */ +public class PrintHtmlFromScreen extends Activity { + + private WebView mWebView; + + private boolean mDataLoaded; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.print_html_from_screen); + mWebView = (WebView) findViewById(R.id.web_view); + + // Important: Only enable the print option after the page is loaded. + mWebView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + // Data loaded, so now we want to show the print option. + mDataLoaded = true; + invalidateOptionsMenu(); + } + }); + + // Load an HTML page. + mWebView.loadUrl("file:///android_res/raw/motogp_stats.html"); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + if (mDataLoaded) { + getMenuInflater().inflate(R.menu.print_custom_content, menu); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_print) { + print(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void print() { + // Get the print manager. + PrintManager printManager = (PrintManager) getSystemService( + Context.PRINT_SERVICE); + // Pass in the ViewView's document adapter. + printManager.print("MotoGP stats", mWebView.createPrintDocumentAdapter(), null); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlOffScreen.java b/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlOffScreen.java new file mode 100644 index 000000000..9c239b867 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/PrintHtmlOffScreen.java @@ -0,0 +1,132 @@ +/* + * 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.example.android.apis.app; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintManager; +import android.view.Menu; +import android.view.MenuItem; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.example.android.apis.R; + +/** + * This class demonstrates how to implement HTML content printing + * from a {@link WebView} which is not shown on the screen. + * <p> + * This activity shows a text prompt and when the user chooses the + * print option from the overflow menu an HTML page with content that + * is not on the screen is printed via an off-screen {@link WebView}. + * </p> + * + * @see PrintManager + * @see WebView + */ +public class PrintHtmlOffScreen extends Activity { + + private WebView mWebView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.print_html_off_screen); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.print_custom_content, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_print) { + print(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void print() { + // Create a WebView and hold on to it as the printing will start when + // load completes and we do not want the WbeView to be garbage collected. + mWebView = new WebView(this); + + // Important: Only after the page is loaded we will do the print. + mWebView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + doPrint(); + } + }); + + // Load an HTML page. + mWebView.loadUrl("file:///android_res/raw/motogp_stats.html"); + } + + private void doPrint() { + // Get the print manager. + PrintManager printManager = (PrintManager) getSystemService( + Context.PRINT_SERVICE); + + // Create a wrapper PrintDocumentAdapter to clean up when done. + PrintDocumentAdapter adapter = new PrintDocumentAdapter() { + private final PrintDocumentAdapter mWrappedInstance = + mWebView.createPrintDocumentAdapter(); + + @Override + public void onStart() { + mWrappedInstance.onStart(); + } + + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback, + Bundle extras) { + mWrappedInstance.onLayout(oldAttributes, newAttributes, cancellationSignal, + callback, extras); + } + + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + mWrappedInstance.onWrite(pages, destination, cancellationSignal, callback); + } + + @Override + public void onFinish() { + mWrappedInstance.onFinish(); + // Intercept the finish call to know when printing is done + // and destroy the WebView as it is expensive to keep around. + mWebView.destroy(); + mWebView = null; + } + }; + + // Pass in the ViewView's document adapter. + printManager.print("MotoGP stats", adapter, null); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/RotationAnimation.java b/samples/ApiDemos/src/com/example/android/apis/app/RotationAnimation.java new file mode 100644 index 000000000..44bd1e6fa --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/RotationAnimation.java @@ -0,0 +1,91 @@ +/* + * 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.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.RadioGroup; + + +public class RotationAnimation extends Activity { + + private int mRotationAnimation = LayoutParams.ROTATION_ANIMATION_ROTATE; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setRotationAnimation(mRotationAnimation); + + setContentView(R.layout.rotation_animation); + + ((CheckBox)findViewById(R.id.windowFullscreen)).setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setFullscreen(isChecked); + } + } + ); + + ((RadioGroup)findViewById(R.id.rotation_radio_group)).setOnCheckedChangeListener( + new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + default: + case R.id.rotate: + mRotationAnimation = LayoutParams.ROTATION_ANIMATION_ROTATE; + break; + case R.id.crossfade: + mRotationAnimation = LayoutParams.ROTATION_ANIMATION_CROSSFADE; + break; + case R.id.jumpcut: + mRotationAnimation = LayoutParams.ROTATION_ANIMATION_JUMPCUT; + break; + } + setRotationAnimation(mRotationAnimation); + } + } + ); + } + + private void setFullscreen(boolean on) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + if (on) { + winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + } else { + winParams.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + } + win.setAttributes(winParams); + } + + private void setRotationAnimation(int rotationAnimation) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + winParams.rotationAnimation = rotationAnimation; + win.setAttributes(winParams); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ScreenOrientation.java b/samples/ApiDemos/src/com/example/android/apis/app/ScreenOrientation.java index 3946b2a9b..01eb5246a 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/ScreenOrientation.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/ScreenOrientation.java @@ -46,6 +46,10 @@ public class ScreenOrientation extends Activity { ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR, + ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE, + ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT, + ActivityInfo.SCREEN_ORIENTATION_FULL_USER, + ActivityInfo.SCREEN_ORIENTATION_LOCKED, }; @Override diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java new file mode 100644 index 000000000..fe871aa8a --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; + +/** + * <h3>Secure Dialog Activity</h3> + * + * <p> + * This activity demonstrates how to create a dialog whose window is backed by + * a secure surface using {@link WindowManager.LayoutParams#FLAG_SECURE}. + * Because the surface is secure, its contents cannot be captured in screenshots + * and will not be visible on non-secure displays even when mirrored. + * </p><p> + * Here are a few things you can do to experiment with secure surfaces and + * observe their behavior. + * <ul> + * <li>Try taking a screenshot. Either the system will prevent you from taking + * a screenshot altogether or the screenshot should not contain the contents + * of the secure surface. + * <li>Try mirroring the secure surface onto a non-secure display such as an + * "Overlay Display" created using the "Simulate secondary displays" option in + * the "Developer options" section of the Settings application. The non-secure + * secondary display should not show the contents of the secure surface. + * <li>Try mirroring the secure surface onto a secure display such as an + * HDMI display with HDCP enabled. The contents of the secure surface should appear + * on the display. + * </ul> + * </p> + */ +public class SecureDialogActivity extends Activity + implements View.OnClickListener { + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // See assets/res/any/layout/secure_dialog_activity.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.secure_dialog_activity); + + // Handle click events on the button to show the dialog. + Button button = (Button)findViewById(R.id.show); + button.setOnClickListener(this); + } + + /** + * Called when the button to show the dialog is clicked. + */ + @Override + public void onClick(View v) { + // Create a dialog. + AlertDialog dialog = new AlertDialog.Builder(this) + .setPositiveButton(android.R.string.ok, null) + .setMessage(R.string.secure_dialog_dialog_text) + .create(); + + // Make the dialog secure. This must be done at the time the dialog is + // created. It cannot be changed after the dialog has been shown. + dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE); + + // Show the dialog. + dialog.show(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java new file mode 100644 index 000000000..978c70c78 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; +import com.example.android.apis.graphics.CubeRenderer; + +import android.app.Activity; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.SurfaceView; +import android.view.WindowManager; + +/** + * <h3>Secure Window Activity</h3> + * + * <p> + * This activity demonstrates how to create a {@link SurfaceView} backed by + * a secure surface using {@link SurfaceView#setSecure}. + * Because the surface is secure, its contents cannot be captured in screenshots + * and will not be visible on non-secure displays even when mirrored. + * </p><p> + * Here are a few things you can do to experiment with secure surfaces and + * observe their behavior. + * <ul> + * <li>Try taking a screenshot. Either the system will prevent you from taking + * a screenshot altogether or the screenshot should not contain the contents + * of the secure surface. + * <li>Try mirroring the secure surface onto a non-secure display such as an + * "Overlay Display" created using the "Simulate secondary displays" option in + * the "Developer options" section of the Settings application. The non-secure + * secondary display should not show the contents of the secure surface. + * <li>Try mirroring the secure surface onto a secure display such as an + * HDMI display with HDCP enabled. The contents of the secure surface should appear + * on the display. + * </ul> + * </p> + */ +public class SecureSurfaceViewActivity extends Activity { + private GLSurfaceView mSurfaceView; + + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // See assets/res/any/layout/secure_surface_view_activity.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.secure_surface_view_activity); + + // Set up the surface view. + // We use a GLSurfaceView in this demonstration but ordinary + // SurfaceViews also support the same secure surface functionality. + mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view); + mSurfaceView.setRenderer(new CubeRenderer(false)); + + // Make the surface view secure. This must be done at the time the surface view + // is created before the surface view's containing window is attached to + // the window manager which happens after onCreate returns. + // It cannot be changed later. + mSurfaceView.setSecure(true); + } + + @Override + protected void onResume() { + // Be sure to call the super class. + super.onResume(); + + // Resume rendering. + mSurfaceView.onResume(); + } + + @Override + protected void onPause() { + // Be sure to call the super class. + super.onPause(); + + // Pause rendering. + mSurfaceView.onPause(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java new file mode 100644 index 000000000..4b5e8b672 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +/** + * <h3>Secure Window Activity</h3> + * + * <p> + * This activity demonstrates how to create an activity whose window is backed by + * a secure surface using {@link WindowManager.LayoutParams#FLAG_SECURE}. + * Because the surface is secure, its contents cannot be captured in screenshots + * and will not be visible on non-secure displays even when mirrored. + * </p><p> + * Here are a few things you can do to experiment with secure surfaces and + * observe their behavior. + * <ul> + * <li>Try taking a screenshot. Either the system will prevent you from taking + * a screenshot altogether or the screenshot should not contain the contents + * of the secure surface. + * <li>Try mirroring the secure surface onto a non-secure display such as an + * "Overlay Display" created using the "Simulate secondary displays" option in + * the "Developer options" section of the Settings application. The non-secure + * secondary display should not show the contents of the secure surface. + * <li>Try mirroring the secure surface onto a secure display such as an + * HDMI display with HDCP enabled. The contents of the secure surface should appear + * on the display. + * </ul> + * </p> + */ +public class SecureWindowActivity extends Activity { + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // See assets/res/any/layout/secure_window_activity.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.secure_window_activity); + + // Make the window secure. This must be done at the time the activity + // is created. It cannot be changed later. + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java b/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java new file mode 100644 index 000000000..412a3c6dc --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java @@ -0,0 +1,348 @@ +/* + * 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.example.android.apis.content; + +import android.app.Activity; +import android.app.LoaderManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.CursorLoader; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.Loader; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.os.Bundle; +import android.provider.ContactsContract; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +/** + * Demonstrates selecting contacts that have changed since a certain time. + */ +public class ChangedContacts extends Activity implements LoaderManager.LoaderCallbacks<Cursor> { + + private static final String CLASS = ChangedContacts.class.getSimpleName(); + + private static final String PREF_KEY_CHANGE = "timestamp_change"; + private static final String PREF_KEY_DELETE = "timestamp_delete"; + + private static final int ID_CHANGE_LOADER = 1; + private static final int ID_DELETE_LOADER = 2; + + /** + * To see this in action, "clear data" for the contacts storage app in the system settings. + * Then come into this app and hit any of the delta buttons. This will cause the contacts + * database to be re-created. + */ + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Toast toast = Toast.makeText(context, "Contacts database created.", Toast.LENGTH_SHORT); + toast.show(); + } + }; + + private DeleteAdapter mDeleteAdapter; + private ChangeAdapter mChangeAdapter; + private long mSearchTime; + private TextView mDisplayView; + private ListView mList; + private Button mDeleteButton; + private Button mChangeButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mDeleteAdapter = new DeleteAdapter(this, null, 0); + mChangeAdapter = new ChangeAdapter(this, null, 0); + + LinearLayout main = new LinearLayout(this); + main.setOrientation(LinearLayout.VERTICAL); + + mChangeButton = new Button(this); + mChangeButton.setText("Changed since " + getLastTimestamp(0, PREF_KEY_CHANGE)); + mChangeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + changeClick(); + } + }); + + mDeleteButton = new Button(this); + mDeleteButton.setText("Deleted since " + getLastTimestamp(0, PREF_KEY_DELETE)); + mDeleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + deleteClick(); + } + }); + + main.addView(mChangeButton); + main.addView(mDeleteButton); + + mDisplayView = new TextView(this); + mDisplayView.setPadding(5, 5, 5, 5); + main.addView(mDisplayView); + + mList = new ListView(this); + mList.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); + main.addView(mList); + + setContentView(main); + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter filter = new IntentFilter(); + filter.addAction(ContactsContract.Intents.CONTACTS_DATABASE_CREATED); + registerReceiver(mReceiver, filter); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mReceiver); + } + + private void changeClick() { + mChangeAdapter.swapCursor(null); + LoaderManager manager = getLoaderManager(); + manager.destroyLoader(ID_DELETE_LOADER); + manager.restartLoader(ID_CHANGE_LOADER, null, this); + } + + private void deleteClick() { + mChangeAdapter.swapCursor(null); + LoaderManager manager = getLoaderManager(); + manager.destroyLoader(ID_CHANGE_LOADER); + manager.restartLoader(ID_DELETE_LOADER, null, this); + } + + private void saveLastTimestamp(long time, String key) { + SharedPreferences pref = getSharedPreferences(CLASS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = pref.edit(); + editor.putLong(key, time); + editor.commit(); + } + + private long getLastTimestamp(long time, String key) { + SharedPreferences pref = getSharedPreferences(CLASS, Context.MODE_PRIVATE); + return pref.getLong(key, time); + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch(id) { + case ID_CHANGE_LOADER: + return getChangeLoader(); + case ID_DELETE_LOADER: + return getDeleteLoader(); + } + return null; + } + + private CursorLoader getChangeLoader() { + String[] projection = new String[]{ + ContactsContract.Data._ID, + ContactsContract.Data.CONTACT_ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + }; + + mSearchTime = getLastTimestamp(0, PREF_KEY_CHANGE); + + String selection = ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?"; + String[] bindArgs = new String[]{mSearchTime + ""}; + return new CursorLoader(this, ContactsContract.Data.CONTENT_URI, projection, + selection, bindArgs, ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + + " desc, " + ContactsContract.Data.CONTACT_ID + " desc"); + } + + private CursorLoader getDeleteLoader() { + String[] projection = new String[]{ + ContactsContract.DeletedContacts.CONTACT_ID, + ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + }; + + mSearchTime = getLastTimestamp(0, PREF_KEY_DELETE); + + String selection = ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?"; + String[] bindArgs = new String[]{mSearchTime + ""}; + return new CursorLoader(this, ContactsContract.DeletedContacts.CONTENT_URI, projection, + selection, bindArgs, ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + + " desc"); + } + + @Override + public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) { + long timestamp = 0; + + + switch (cursorLoader.getId()) { + case ID_CHANGE_LOADER: + mDisplayView.setText(data.getCount() + " change(s) since " + mSearchTime); + mList.setAdapter(mChangeAdapter); + mChangeAdapter.swapCursor(data); + + // Save the largest timestamp returned. Only need the first one due to the sort + // order. + if (data.moveToNext()) { + timestamp = data.getLong(data.getColumnIndex( + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP)); + data.moveToPrevious(); + } + if (timestamp > 0) { + saveLastTimestamp(timestamp, PREF_KEY_CHANGE); + mChangeButton.setText("Changed since " + timestamp); + } + break; + case ID_DELETE_LOADER: + mDisplayView.setText(data.getCount() + " delete(s) since " + mSearchTime); + mList.setAdapter(mDeleteAdapter); + mDeleteAdapter.swapCursor(new DeleteCursorWrapper(data)); + if (data.moveToNext()) { + timestamp = data.getLong(data.getColumnIndex( + ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP)); + data.moveToPrevious(); + } + if (timestamp > 0) { + saveLastTimestamp(timestamp, PREF_KEY_DELETE); + mDeleteButton.setText("Deleted since " + timestamp); + } + break; + } + } + + @Override + public void onLoaderReset(Loader<Cursor> cursorLoader) { + mDisplayView.setText(""); + switch (cursorLoader.getId()) { + case ID_CHANGE_LOADER: + mChangeAdapter.swapCursor(null); + break; + case ID_DELETE_LOADER: + mDeleteAdapter.swapCursor(null); + break; + } + } + + private class DeleteCursorWrapper extends CursorWrapper { + + /** + * Creates a cursor wrapper. + * + * @param cursor The underlying cursor to wrap. + */ + public DeleteCursorWrapper(Cursor cursor) { + super(cursor); + } + + @Override + public int getColumnIndexOrThrow(String columnName) { + if (columnName.equals("_id")) { + return super.getColumnIndex(ContactsContract.DeletedContacts.CONTACT_ID); + } + return super.getColumnIndex(columnName); + } + } + + private static class DeleteAdapter extends CursorAdapter { + + private Context mContext; + + public DeleteAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + this.mContext = context; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + LinearLayout item = new LinearLayout(mContext); + item.addView(buildText(context)); + item.addView(buildText(context)); + return item; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + LinearLayout item = (LinearLayout) view; + String id = cursor.getString(cursor.getColumnIndex( + ContactsContract.DeletedContacts.CONTACT_ID)); + String timestamp = cursor.getString(cursor.getColumnIndex( + ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP)); + + setText(item.getChildAt(0), id); + setText(item.getChildAt(1), timestamp); + } + } + + private static class ChangeAdapter extends CursorAdapter { + + private Context mContext; + + public ChangeAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mContext = context; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + LinearLayout item = new LinearLayout(mContext); + item.addView(buildText(context)); + item.addView(buildText(context)); + item.addView(buildText(context)); + return item; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + LinearLayout item = (LinearLayout) view; + + String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)); + String name = cursor.getString(cursor.getColumnIndex( + ContactsContract.Data.DISPLAY_NAME)); + String timestamp = cursor.getString(cursor.getColumnIndex( + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP)); + + setText(item.getChildAt(0), id); + setText(item.getChildAt(1), name); + setText(item.getChildAt(2), timestamp); + } + } + + private static void setText(View view, String value) { + TextView text = (TextView) view; + text.setText(value); + } + + private static TextView buildText(Context context) { + TextView view = new TextView(context); + view.setPadding(3, 3, 3, 3); + return view; + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java b/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java index 156625a67..f26bcdaf4 100644 --- a/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java +++ b/samples/ApiDemos/src/com/example/android/apis/content/FileProvider.java @@ -26,9 +26,11 @@ import android.content.ContentValues; import android.content.ContentProvider.PipeDataWriter; import android.content.res.AssetFileDescriptor; import android.database.Cursor; +import android.database.MatrixCursor; import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; import android.util.Log; /** @@ -45,8 +47,43 @@ public class FileProvider extends ContentProvider @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - // Don't support queries. - return null; + + // content providers that support open and openAssetFile should support queries for all + // android.provider.OpenableColumns. + + int displayNameIndex = -1; + int sizeIndex = -1; + + // If projection is null, return all columns. + if (projection == null) { + projection = new String[] { + OpenableColumns.DISPLAY_NAME, + OpenableColumns.SIZE}; + } + + for (int i = 0; i < projection.length; i++) { + if (OpenableColumns.DISPLAY_NAME.equals(projection[i])) { + displayNameIndex = i; + } + if (OpenableColumns.SIZE.equals(projection[i])) { + sizeIndex = i; + } + } + + MatrixCursor cursor = new MatrixCursor(projection); + Object[] result = new Object[projection.length]; + + for (int i = 0; i < result.length; i++) { + if (i == displayNameIndex) { + result[i] = uri.getPath(); + } + if (i == sizeIndex) { + result[i] = null; // Size is unknown, so null, if it was known, it would go here. + } + } + + cursor.addRow(result); + return cursor; } @Override diff --git a/samples/ApiDemos/src/com/example/android/apis/content/TextUndoActivity.java b/samples/ApiDemos/src/com/example/android/apis/content/TextUndoActivity.java new file mode 100644 index 000000000..6b9dc8e62 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/content/TextUndoActivity.java @@ -0,0 +1,73 @@ +/* + * 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.example.android.apis.content; + +import android.app.Activity; +//import android.content.UndoManager; +import android.os.Parcelable; +import android.view.View; +import android.widget.Button; +import com.example.android.apis.R; + +import android.os.Bundle; +import android.widget.TextView; + +/** + * Simple example of using an UndoManager for editing text in a TextView. + */ +public class TextUndoActivity extends Activity { + //UndoManager mUndoManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + /* + mUndoManager = new UndoManager(); + if (savedInstanceState != null) { + Parcelable p = savedInstanceState.getParcelable("undo"); + if (p != null) { + mUndoManager.restoreInstanceState(p); + } + } + */ + + setContentView(R.layout.text_undo); + + /* + ((TextView)findViewById(R.id.text)).setUndoManager(mUndoManager, "text"); + ((Button)findViewById(R.id.undo)).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mUndoManager.undo(null, 1); + } + }); + ((Button)findViewById(R.id.redo)).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mUndoManager.redo(null, 1); + } + }); + */ + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + //outState.putParcelable("undo", mUndoManager.saveInstanceState()); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java b/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java index ac0ae27e3..b30808c53 100644 --- a/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java +++ b/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java @@ -25,7 +25,7 @@ import android.opengl.GLSurfaceView; * Render a pair of tumbling cubes. */ -class CubeRenderer implements GLSurfaceView.Renderer { +public class CubeRenderer implements GLSurfaceView.Renderer { public CubeRenderer(boolean useTranslucentBackground) { mTranslucentBackground = useTranslucentBackground; mCube = new Cube(); diff --git a/samples/ApiDemos/src/com/example/android/apis/hardware/ConsumerIr.java b/samples/ApiDemos/src/com/example/android/apis/hardware/ConsumerIr.java new file mode 100644 index 000000000..c0ae960ea --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/hardware/ConsumerIr.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 20013The 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.example.android.apis.hardware; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.hardware.ConsumerIrManager; +import android.view.View; +import android.widget.TextView; +import android.util.Log; + +import com.example.android.apis.R; + +/** + * App that transmit an IR code + * + * <p>This demonstrates the {@link android.hardware.ConsumerIrManager android.hardware.ConsumerIrManager} class. + * + * <h4>Demo</h4> + * Hardware / Consumer IR + * + * <h4>Source files</h4> + * <table class="LinkTable"> + * <tr> + * <td>src/com.example.android.apis/hardware/ConsumerIr.java</td> + * <td>Consumer IR demo</td> + * </tr> + * <tr> + * <td>res/any/layout/consumer_ir.xml</td> + * <td>Defines contents of the screen</td> + * </tr> + * </table> + */ +public class ConsumerIr extends Activity { + private static final String TAG = "ConsumerIrTest"; + TextView mFreqsText; + ConsumerIrManager mCIR; + + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Be sure to call the super class. + super.onCreate(savedInstanceState); + + // Get a reference to the ConsumerIrManager + mCIR = (ConsumerIrManager)getSystemService(Context.CONSUMER_IR_SERVICE); + + // See assets/res/any/layout/consumer_ir.xml for this + // view layout definition, which is being set here as + // the content of our screen. + setContentView(R.layout.consumer_ir); + + // Set the OnClickListener for the button so we see when it's pressed. + findViewById(R.id.send_button).setOnClickListener(mSendClickListener); + findViewById(R.id.get_freqs_button).setOnClickListener(mGetFreqsClickListener); + mFreqsText = (TextView) findViewById(R.id.freqs_text); + } + + View.OnClickListener mSendClickListener = new View.OnClickListener() { + public void onClick(View v) { + if (!mCIR.hasIrEmitter()) { + Log.e(TAG, "No IR Emitter found\n"); + return; + } + + // A pattern of alternating series of carrier on and off periods measured in + // microseconds. + int[] pattern = {1901, 4453, 625, 1614, 625, 1588, 625, 1614, 625, 442, 625, 442, 625, + 468, 625, 442, 625, 494, 572, 1614, 625, 1588, 625, 1614, 625, 494, 572, 442, 651, + 442, 625, 442, 625, 442, 625, 1614, 625, 1588, 651, 1588, 625, 442, 625, 494, 598, + 442, 625, 442, 625, 520, 572, 442, 625, 442, 625, 442, 651, 1588, 625, 1614, 625, + 1588, 625, 1614, 625, 1588, 625, 48958}; + + // transmit the pattern at 38.4KHz + mCIR.transmit(38400, pattern); + } + }; + + View.OnClickListener mGetFreqsClickListener = new View.OnClickListener() { + public void onClick(View v) { + StringBuilder b = new StringBuilder(); + + if (!mCIR.hasIrEmitter()) { + mFreqsText.setText("No IR Emitter found!"); + Log.e(TAG, "No IR Emitter found!\n"); + return; + } + + // Get the available carrier frequency ranges + ConsumerIrManager.CarrierFrequencyRange[] freqs = mCIR.getCarrierFrequencies(); + b.append("IR Carrier Frequencies:\n"); + for (ConsumerIrManager.CarrierFrequencyRange range : freqs) { + b.append(String.format(" %d - %d\n", range.getMinFrequency(), + range.getMaxFrequency())); + } + mFreqsText.setText(b.toString()); + } + }; +} diff --git a/samples/ApiDemos/src/com/example/android/apis/media/VideoViewDemo.java b/samples/ApiDemos/src/com/example/android/apis/media/VideoViewDemo.java index e72c077a6..dacff56f2 100644 --- a/samples/ApiDemos/src/com/example/android/apis/media/VideoViewDemo.java +++ b/samples/ApiDemos/src/com/example/android/apis/media/VideoViewDemo.java @@ -18,6 +18,7 @@ package com.example.android.apis.media; import com.example.android.apis.R; import android.app.Activity; +import android.net.Uri; import android.os.Bundle; import android.widget.MediaController; import android.widget.Toast; @@ -29,7 +30,6 @@ public class VideoViewDemo extends Activity { * TODO: Set the path variable to a streaming video URL or a local media * file path. */ - private String path = ""; private VideoView mVideoView; @Override @@ -38,24 +38,13 @@ public class VideoViewDemo extends Activity { setContentView(R.layout.videoview); mVideoView = (VideoView) findViewById(R.id.surface_view); - if (path == "") { - // Tell the user to provide a media file URL/path. - Toast.makeText( - VideoViewDemo.this, - "Please edit VideoViewDemo Activity, and set path" - + " variable to your media file URL/path", - Toast.LENGTH_LONG).show(); - } else { - - /* - * Alternatively,for streaming media you can use - * mVideoView.setVideoURI(Uri.parse(URLstring)); - */ - mVideoView.setVideoPath(path); - mVideoView.setMediaController(new MediaController(this)); - mVideoView.requestFocus(); - - } + /* + * Alternatively, you can use mVideoView.setVideoPath(<path>); + */ + mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + + "/" + R.raw.videoviewdemo)); + mVideoView.setMediaController(new MediaController(this)); + mVideoView.requestFocus(); } } diff --git a/samples/ApiDemos/src/com/example/android/apis/os/TriggerSensors.java b/samples/ApiDemos/src/com/example/android/apis/os/TriggerSensors.java new file mode 100644 index 000000000..7d4b4168c --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/os/TriggerSensors.java @@ -0,0 +1,113 @@ +/* + * 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.example.android.apis.os; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.hardware.Sensor; +import android.hardware.TriggerEvent; +import android.hardware.TriggerEventListener; +import android.hardware.SensorManager; +import android.os.Bundle; +import android.widget.TextView; +import com.example.android.apis.R; + +/** + * <h3>Application showing the Trigger Sensor API for the Significant Motion sensor. </h3> + +<p>This demonstrates the {@link android.hardware.SensorManager android.hardware.SensorManager + android.hardware.TriggerEventListener} class. + +<h4>Demo</h4> +OS / TriggerSensors + +<h4>Source files</h4> + * <table class="LinkTable"> + * <tr> + * <td >src/com.example.android.apis/os/TriggerSensors.java</td> + * <td >TriggerSensors</td> + * </tr> + * </table> + */ + + +class TriggerListener extends TriggerEventListener { + private Context mContext; + private TextView mTextView; + + TriggerListener(Context context, TextView textView) { + mContext = context; + mTextView = textView; + } + + @Override + public void onTrigger(TriggerEvent event) { + if (event.values[0] == 1) { + mTextView.append(mContext.getString(R.string.sig_motion) + "\n"); + mTextView.append(mContext.getString(R.string.sig_motion_auto_disabled) + "\n"); + } + // Sensor is auto disabled. + } +} + +public class TriggerSensors extends Activity { + private SensorManager mSensorManager; + private Sensor mSigMotion; + private TriggerListener mListener; + private TextView mTextView; + + @Override + protected void onResume() { + super.onResume(); + if (mSigMotion != null && mSensorManager.requestTriggerSensor(mListener, mSigMotion)) + mTextView.append(getString(R.string.sig_motion_enabled) + "\n"); + } + + @Override + protected void onPause() { + super.onPause(); + // Call disable only if needed for cleanup. + // The sensor is auto disabled when triggered. + if (mSigMotion != null) mSensorManager.cancelTriggerSensor(mListener, mSigMotion); + } + + + /** + * Initialization of the Activity after it is first created. Must at least + * call {@link android.app.Activity#setContentView setContentView()} to + * describe what is to be displayed in the screen. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.trigger_sensors); + mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); + mSigMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); + mTextView = (TextView)findViewById(R.id.text); + mListener = new TriggerListener(this, mTextView); + if (mSigMotion == null) { + mTextView.append(getString(R.string.no_sig_motion) + "\n"); + } + } + + @Override + protected void onStop() { + if (mSigMotion != null) mSensorManager.cancelTriggerSensor(mListener, mSigMotion); + super.onStop(); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java b/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java new file mode 100644 index 000000000..901806ac5 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java @@ -0,0 +1,526 @@ +/* + * Copyright 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.example.android.apis.security; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.content.Context; +import android.database.DataSetObserver; +import android.os.AsyncTask; +import android.os.Bundle; +import android.security.KeyPairGeneratorSpec; +import android.util.Base64; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +public class KeyStoreUsage extends Activity { + private static final String TAG = "AndroidKeyStoreUsage"; + + /** + * An instance of {@link java.security.KeyStore} through which this app + * talks to the {@code AndroidKeyStore}. + */ + KeyStore mKeyStore; + + /** + * Used by the {@code ListView} in our layout to list the keys available in + * our {@code KeyStore} by their alias names. + */ + AliasAdapter mAdapter; + + /** + * Button in the UI that causes a new keypair to be generated in the + * {@code KeyStore}. + */ + Button mGenerateButton; + + /** + * Button in the UI that causes data to be signed by a key we selected from + * the list available in the {@code KeyStore}. + */ + Button mSignButton; + + /** + * Button in the UI that causes data to be signed by a key we selected from + * the list available in the {@code KeyStore}. + */ + Button mVerifyButton; + + /** + * Button in the UI that causes a key entry to be deleted from the + * {@code KeyStore}. + */ + Button mDeleteButton; + + /** + * Text field in the UI that holds plaintext. + */ + EditText mPlainText; + + /** + * Text field in the UI that holds the signature. + */ + EditText mCipherText; + + /** + * The alias of the selected entry in the KeyStore. + */ + private String mSelectedAlias; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.keystore_usage); + + /* + * Set up our {@code ListView} with an adapter that allows + * us to choose from the available entry aliases. + */ + ListView lv = (ListView) findViewById(R.id.entries_list); + mAdapter = new AliasAdapter(getApplicationContext()); + lv.setAdapter(mAdapter); + lv.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + mSelectedAlias = mAdapter.getItem(position); + setKeyActionButtonsEnabled(true); + } + }); + + // This is alias the user wants for a generated key. + final EditText aliasInput = (EditText) findViewById(R.id.entry_name); + mGenerateButton = (Button) findViewById(R.id.generate_button); + mGenerateButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + /* + * When the user presses the "Generate" button, we'll + * check the alias isn't blank here. + */ + final String alias = aliasInput.getText().toString(); + if (alias == null || alias.length() == 0) { + aliasInput.setError(getResources().getText(R.string.keystore_no_alias_error)); + } else { + /* + * It's not blank, so disable the generate button while + * the generation of the key is happening. It will be + * enabled by the {@code AsyncTask} later after its + * work is done. + */ + aliasInput.setError(null); + mGenerateButton.setEnabled(false); + new GenerateTask().execute(alias); + } + } + }); + + mSignButton = (Button) findViewById(R.id.sign_button); + mSignButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + final String data = mPlainText.getText().toString(); + if (alias != null) { + setKeyActionButtonsEnabled(false); + new SignTask().execute(alias, data); + } + } + }); + + mVerifyButton = (Button) findViewById(R.id.verify_button); + mVerifyButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + final String data = mPlainText.getText().toString(); + final String signature = mCipherText.getText().toString(); + if (alias != null) { + setKeyActionButtonsEnabled(false); + new VerifyTask().execute(alias, data, signature); + } + } + }); + + mDeleteButton = (Button) findViewById(R.id.delete_button); + mDeleteButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final String alias = mSelectedAlias; + if (alias != null) { + setKeyActionButtonsEnabled(false); + new DeleteTask().execute(alias); + } + } + }); + + mPlainText = (EditText) findViewById(R.id.plaintext); + mPlainText.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mPlainText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + }); + + mCipherText = (EditText) findViewById(R.id.ciphertext); + mCipherText.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mCipherText + .setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + }); + + updateKeyList(); + } + + private class AliasAdapter extends ArrayAdapter<String> { + public AliasAdapter(Context context) { + // We want users to choose a key, so use the appropriate layout. + super(context, android.R.layout.simple_list_item_single_choice); + } + + /** + * This clears out all previous aliases and replaces it with the + * current entries. + */ + public void setAliases(List<String> items) { + clear(); + addAll(items); + notifyDataSetChanged(); + } + } + + private void updateKeyList() { + setKeyActionButtonsEnabled(false); + new UpdateKeyListTask().execute(); + } + + /** + * Sets all the buttons related to actions that act on an existing key to + * enabled or disabled. + */ + private void setKeyActionButtonsEnabled(boolean enabled) { + mSignButton.setEnabled(enabled); + mVerifyButton.setEnabled(enabled); + mDeleteButton.setEnabled(enabled); + } + + private class UpdateKeyListTask extends AsyncTask<Void, Void, Enumeration<String>> { + @Override + protected Enumeration<String> doInBackground(Void... params) { + try { +// BEGIN_INCLUDE(list) + /* + * Load the Android KeyStore instance using the the + * "AndroidKeyStore" provider to list out what entries are + * currently stored. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + Enumeration<String> aliases = ks.aliases(); +// END_INCLUDE(list) + return aliases; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (CertificateException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } catch (IOException e) { + Log.w(TAG, "Could not list keys", e); + return null; + } + } + + @Override + protected void onPostExecute(Enumeration<String> result) { + List<String> aliases = new ArrayList<String>(); + while (result.hasMoreElements()) { + aliases.add(result.nextElement()); + } + mAdapter.setAliases(aliases); + } + } + + private class GenerateTask extends AsyncTask<String, Void, Boolean> { + @Override + protected Boolean doInBackground(String... params) { + final String alias = params[0]; + try { +// BEGIN_INCLUDE(generate) + /* + * Generate a new entry in the KeyStore by using the + * KeyPairGenerator API. We have to specify the attributes for a + * self-signed X.509 certificate here so the KeyStore can attach + * the public key part to it. It can be replaced later with a + * certificate signed by a Certificate Authority (CA) if needed. + */ + Calendar cal = Calendar.getInstance(); + Date now = cal.getTime(); + cal.add(Calendar.YEAR, 1); + Date end = cal.getTime(); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); + kpg.initialize(new KeyPairGeneratorSpec.Builder(getApplicationContext()) + .setAlias(alias) + .setStartDate(now) + .setEndDate(end) + .setSerialNumber(BigInteger.valueOf(1)) + .setSubject(new X500Principal("CN=test1")) + .build()); + + KeyPair kp = kpg.generateKeyPair(); +// END_INCLUDE(generate) + return true; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (InvalidAlgorithmParameterException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (NoSuchProviderException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + updateKeyList(); + mGenerateButton.setEnabled(true); + } + + @Override + protected void onCancelled() { + mGenerateButton.setEnabled(true); + } + } + + private class SignTask extends AsyncTask<String, Void, String> { + @Override + protected String doInBackground(String... params) { + final String alias = params[0]; + final String dataString = params[1]; + try { + byte[] data = dataString.getBytes(); +// BEGIN_INCLUDE(sign) + /* + * Use a PrivateKey in the KeyStore to create a signature over + * some data. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + KeyStore.Entry entry = ks.getEntry(alias, null); + if (!(entry instanceof PrivateKeyEntry)) { + Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + return null; + } + Signature s = Signature.getInstance("SHA256withRSA"); + s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); + s.update(data); + byte[] signature = s.sign(); +// END_INCLUDE(sign) + return Base64.encodeToString(signature, Base64.DEFAULT); + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (UnrecoverableEntryException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (InvalidKeyException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } catch (SignatureException e) { + Log.w(TAG, "Could not generate key", e); + return null; + } + } + + @Override + protected void onPostExecute(String result) { + mCipherText.setText(result); + setKeyActionButtonsEnabled(true); + } + + @Override + protected void onCancelled() { + mCipherText.setText("error!"); + setKeyActionButtonsEnabled(true); + } + } + + private class VerifyTask extends AsyncTask<String, Void, Boolean> { + @Override + protected Boolean doInBackground(String... params) { + final String alias = params[0]; + final String dataString = params[1]; + final String signatureString = params[2]; + try { + byte[] data = dataString.getBytes(); + byte[] signature; + try { + signature = Base64.decode(signatureString, Base64.DEFAULT); + } catch (IllegalArgumentException e) { + signature = new byte[0]; + } +// BEGIN_INCLUDE(verify) + /* + * Verify a signature previously made by a PrivateKey in our + * KeyStore. This uses the X.509 certificate attached to our + * private key in the KeyStore to validate a previously + * generated signature. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + KeyStore.Entry entry = ks.getEntry(alias, null); + if (!(entry instanceof PrivateKeyEntry)) { + Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + return false; + } + Signature s = Signature.getInstance("SHA256withRSA"); + s.initVerify(((PrivateKeyEntry) entry).getCertificate()); + s.update(data); + boolean valid = s.verify(signature); +// END_INCLUDE(verify) + return valid; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (UnrecoverableEntryException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (InvalidKeyException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } catch (SignatureException e) { + Log.w(TAG, "Could not generate key", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + mCipherText.setTextColor(getResources().getColor(R.color.solid_green)); + } else { + mCipherText.setTextColor(getResources().getColor(R.color.solid_red)); + } + setKeyActionButtonsEnabled(true); + } + + @Override + protected void onCancelled() { + mCipherText.setText("error!"); + setKeyActionButtonsEnabled(true); + mCipherText.setTextColor(getResources().getColor(android.R.color.primary_text_dark)); + } + } + + private class DeleteTask extends AsyncTask<String, Void, Void> { + @Override + protected Void doInBackground(String... params) { + final String alias = params[0]; + try { +// BEGIN_INCLUDE(delete) + /* + * Deletes a previously generated or stored entry in the + * KeyStore. + */ + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + ks.deleteEntry(alias); +// END_INCLUDE(delete) + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Could not generate key", e); + } catch (KeyStoreException e) { + Log.w(TAG, "Could not generate key", e); + } catch (CertificateException e) { + Log.w(TAG, "Could not generate key", e); + } catch (IOException e) { + Log.w(TAG, "Could not generate key", e); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + updateKeyList(); + } + + @Override + protected void onCancelled() { + updateKeyList(); + } + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserNavActivity.java b/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserNavActivity.java new file mode 100644 index 000000000..2c5f7bf20 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserNavActivity.java @@ -0,0 +1,266 @@ +/* + * 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.example.android.apis.view; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.ScrollView; +import android.widget.SearchView; +import android.widget.SeekBar; +import android.widget.ShareActionProvider; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.SearchView.OnQueryTextListener; + +import com.example.android.apis.R; + +/** + * This activity demonstrates how to use system UI flags to implement + * a content browser style of UI (such as a book reader) that hides the + * nav bar as well as the status bar. + */ +public class ContentBrowserNavActivity extends Activity + implements OnQueryTextListener, ActionBar.TabListener { + + /** + * Implementation of a view for displaying immersive content, using system UI + * flags to transition in and out of modes where the user is focused on that + * content. + */ +//BEGIN_INCLUDE(content) + public static class Content extends ScrollView + implements View.OnSystemUiVisibilityChangeListener, View.OnClickListener { + TextView mText; + TextView mTitleView; + SeekBar mSeekView; + boolean mNavVisible; + int mBaseSystemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + int mLastSystemUiVis; + + Runnable mNavHider = new Runnable() { + @Override public void run() { + setNavVisibility(false); + } + }; + + public Content(Context context, AttributeSet attrs) { + super(context, attrs); + + mText = new TextView(context); + mText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + mText.setText(context.getString(R.string.alert_dialog_two_buttons2ultra_msg)); + mText.setClickable(false); + mText.setOnClickListener(this); + mText.setTextIsSelectable(true); + addView(mText, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + setOnSystemUiVisibilityChangeListener(this); + } + + public void init(TextView title, SeekBar seek) { + // This called by the containing activity to supply the surrounding + // state of the content browser that it will interact with. + mTitleView = title; + mSeekView = seek; + setNavVisibility(true); + } + + @Override public void onSystemUiVisibilityChange(int visibility) { + // Detect when we go out of low-profile mode, to also go out + // of full screen. We only do this when the low profile mode + // is changing from its last state, and turning off. + int diff = mLastSystemUiVis ^ visibility; + mLastSystemUiVis = visibility; + if ((diff&SYSTEM_UI_FLAG_LOW_PROFILE) != 0 + && (visibility&SYSTEM_UI_FLAG_LOW_PROFILE) == 0) { + setNavVisibility(true); + } + } + + @Override protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + + // When we become visible, we show our navigation elements briefly + // before hiding them. + setNavVisibility(true); + getHandler().postDelayed(mNavHider, 2000); + } + + @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + + // When the user scrolls, we hide navigation elements. + setNavVisibility(false); + } + + @Override public void onClick(View v) { + // When the user clicks, we toggle the visibility of navigation elements. + int curVis = getSystemUiVisibility(); + setNavVisibility((curVis&SYSTEM_UI_FLAG_LOW_PROFILE) != 0); + } + + void setBaseSystemUiVisibility(int visibility) { + mBaseSystemUiVisibility = visibility; + } + + void setNavVisibility(boolean visible) { + int newVis = mBaseSystemUiVisibility; + if (!visible) { + newVis |= SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_FULLSCREEN + | SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE; + } + final boolean changed = newVis == getSystemUiVisibility(); + + // Unschedule any pending event to hide navigation if we are + // changing the visibility, or making the UI visible. + if (changed || visible) { + Handler h = getHandler(); + if (h != null) { + h.removeCallbacks(mNavHider); + } + } + + // Set the new desired visibility. + setSystemUiVisibility(newVis); + mTitleView.setVisibility(visible ? VISIBLE : INVISIBLE); + mSeekView.setVisibility(visible ? VISIBLE : INVISIBLE); + } + } +//END_INCLUDE(content) + + Content mContent; + + public ContentBrowserNavActivity() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + + setContentView(R.layout.content_browser_nav); + mContent = (Content)findViewById(R.id.content); + mContent.init((TextView)findViewById(R.id.title), + (SeekBar)findViewById(R.id.seekbar)); + + ActionBar bar = getActionBar(); + bar.addTab(bar.newTab().setText("Tab 1").setTabListener(this)); + bar.addTab(bar.newTab().setText("Tab 2").setTabListener(this)); + bar.addTab(bar.newTab().setText("Tab 3").setTabListener(this)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.content_actions, menu); + SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); + searchView.setOnQueryTextListener(this); + + // Set file with share history to the provider and set the share intent. + MenuItem actionItem = menu.findItem(R.id.menu_item_share_action_provider_action_bar); + ShareActionProvider actionProvider = (ShareActionProvider) actionItem.getActionProvider(); + actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME); + // Note that you can set/change the intent any time, + // say when the user has selected an image. + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("image/*"); + Uri uri = Uri.fromFile(getFileStreamPath("shared.png")); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + actionProvider.setShareIntent(shareIntent); + return true; + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + /** + * This method is declared in the menu. + */ + public void onSort(MenuItem item) { + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.show_tabs: + getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + item.setChecked(true); + return true; + case R.id.hide_tabs: + getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + item.setChecked(true); + return true; + case R.id.stable_layout: + item.setChecked(!item.isChecked()); + mContent.setBaseSystemUiVisibility(item.isChecked() + ? View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + : View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + return true; + } + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + Toast.makeText(this, "Searching for: " + query + "...", Toast.LENGTH_SHORT).show(); + return true; + } + + @Override + public void onTabSelected(Tab tab, FragmentTransaction ft) { + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java b/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java new file mode 100644 index 000000000..dfd886a60 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java @@ -0,0 +1,245 @@ +/* + * 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.example.android.apis.view; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import android.graphics.Rect; +import com.example.android.apis.R; + +//BEGIN_INCLUDE(Complete) +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RemoteViews; + +/** + * Example of writing a custom layout manager. This is a fairly full-featured + * layout manager that is relatively general, handling all layout cases. You + * can simplify it for more specific cases. + */ +@RemoteViews.RemoteView +public class CustomLayout extends ViewGroup { + /** The amount of space used by children in the left gutter. */ + private int mLeftWidth; + + /** The amount of space used by children in the right gutter. */ + private int mRightWidth; + + /** These are used for computing child frames based on their gravity. */ + private final Rect mTmpContainerRect = new Rect(); + private final Rect mTmpChildRect = new Rect(); + + public CustomLayout(Context context) { + super(context); + } + + public CustomLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustomLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Any layout manager that doesn't scroll will want this. + */ + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + /** + * Ask all children to measure themselves and compute the measurement of this + * layout based on the children. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int count = getChildCount(); + + // These keep track of the space we are using on the left and right for + // views positioned there; we need member variables so we can also use + // these for layout later. + mLeftWidth = 0; + mRightWidth = 0; + + // Measurement will ultimately be computing these values. + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + // Iterate through all children, measuring them and computing our dimensions + // from their size. + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + // Measure the child. + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + + // Update our size information based on the layout params. Children + // that asked to be positioned on the left or right go in those gutters. + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.position == LayoutParams.POSITION_LEFT) { + mLeftWidth += Math.max(maxWidth, + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + } else if (lp.position == LayoutParams.POSITION_RIGHT) { + mRightWidth += Math.max(maxWidth, + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + } else { + maxWidth = Math.max(maxWidth, + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + } + maxHeight = Math.max(maxHeight, + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, child.getMeasuredState()); + } + } + + // Total width is the maximum width of all inner children plus the gutters. + maxWidth += mLeftWidth + mRightWidth; + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + // Report our final dimensions. + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + + /** + * Position all children within this layout. + */ + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int count = getChildCount(); + + // These are the far left and right edges in which we are performing layout. + int leftPos = getPaddingLeft(); + int rightPos = right - left - getPaddingRight(); + + // This is the middle region inside of the gutter. + final int middleLeft = leftPos + mLeftWidth; + final int middleRight = rightPos - mRightWidth; + + // These are the top and bottom edges in which we are performing layout. + final int parentTop = getPaddingTop(); + final int parentBottom = bottom - top - getPaddingBottom(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + // Compute the frame in which we are placing this child. + if (lp.position == LayoutParams.POSITION_LEFT) { + mTmpContainerRect.left = leftPos + lp.leftMargin; + mTmpContainerRect.right = leftPos + width + lp.rightMargin; + leftPos = mTmpContainerRect.right; + } else if (lp.position == LayoutParams.POSITION_RIGHT) { + mTmpContainerRect.right = rightPos - lp.rightMargin; + mTmpContainerRect.left = rightPos - width - lp.leftMargin; + rightPos = mTmpContainerRect.left; + } else { + mTmpContainerRect.left = middleLeft + lp.leftMargin; + mTmpContainerRect.right = middleRight - lp.rightMargin; + } + mTmpContainerRect.top = parentTop + lp.topMargin; + mTmpContainerRect.bottom = parentBottom - lp.bottomMargin; + + // Use the child's gravity and size to determine its final + // frame within its container. + Gravity.apply(lp.gravity, width, height, mTmpContainerRect, mTmpChildRect); + + // Place the child. + child.layout(mTmpChildRect.left, mTmpChildRect.top, + mTmpChildRect.right, mTmpChildRect.bottom); + } + } + } + + // ---------------------------------------------------------------------- + // The rest of the implementation is for custom per-child layout parameters. + // If you do not need these (for example you are writing a layout manager + // that does fixed positioning of its children), you can drop all of this. + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new CustomLayout.LayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + /** + * Custom per-child layout information. + */ + public static class LayoutParams extends MarginLayoutParams { + /** + * The gravity to apply with the View to which these layout parameters + * are associated. + */ + public int gravity = Gravity.TOP | Gravity.START; + + public static int POSITION_MIDDLE = 0; + public static int POSITION_LEFT = 1; + public static int POSITION_RIGHT = 2; + + public int position = POSITION_MIDDLE; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + + // Pull the layout param values from the layout XML during + // inflation. This is not needed if you don't care about + // changing the layout behavior in XML. + TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomLayoutLP); + gravity = a.getInt(R.styleable.CustomLayoutLP_android_layout_gravity, gravity); + position = a.getInt(R.styleable.CustomLayoutLP_layout_position, position); + a.recycle(); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } +} +//END_INCLUDE(Complete) diff --git a/samples/ApiDemos/src/com/example/android/apis/view/CustomLayoutActivity.java b/samples/ApiDemos/src/com/example/android/apis/view/CustomLayoutActivity.java new file mode 100644 index 000000000..171656933 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/CustomLayoutActivity.java @@ -0,0 +1,33 @@ +/* + * 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.example.android.apis.view; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; + +public class CustomLayoutActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.custom_layout); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/DateWidgets1.java b/samples/ApiDemos/src/com/example/android/apis/view/DateWidgets1.java index f0b1d222e..537bda896 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/DateWidgets1.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/DateWidgets1.java @@ -51,8 +51,9 @@ public class DateWidgets1 extends Activity { private int mHour; private int mMinute; - static final int TIME_DIALOG_ID = 0; - static final int DATE_DIALOG_ID = 1; + static final int TIME_12_DIALOG_ID = 0; + static final int TIME_24_DIALOG_ID = 1; + static final int DATE_DIALOG_ID = 2; @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,21 +63,9 @@ public class DateWidgets1 extends Activity { mDateDisplay = (TextView) findViewById(R.id.dateDisplay); - Button pickDate = (Button) findViewById(R.id.pickDate); - pickDate.setOnClickListener(new View.OnClickListener() { - - public void onClick(View v) { - showDialog(DATE_DIALOG_ID); - } - }); - - Button pickTime = (Button) findViewById(R.id.pickTime); - pickTime.setOnClickListener(new View.OnClickListener() { - - public void onClick(View v) { - showDialog(TIME_DIALOG_ID); - } - }); + setDialogOnClickListener(R.id.pickDate, DATE_DIALOG_ID); + setDialogOnClickListener(R.id.pickTime12, TIME_12_DIALOG_ID); + setDialogOnClickListener(R.id.pickTime24, TIME_24_DIALOG_ID); final Calendar c = Calendar.getInstance(); mYear = c.get(Calendar.YEAR); @@ -88,12 +77,22 @@ public class DateWidgets1 extends Activity { updateDisplay(); } + private void setDialogOnClickListener(int buttonId, final int dialogId) { + Button b = (Button) findViewById(buttonId); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + showDialog(dialogId); + } + }); + } + @Override protected Dialog onCreateDialog(int id) { switch (id) { - case TIME_DIALOG_ID: + case TIME_12_DIALOG_ID: + case TIME_24_DIALOG_ID: return new TimePickerDialog(this, - mTimeSetListener, mHour, mMinute, false); + mTimeSetListener, mHour, mMinute, id == TIME_24_DIALOG_ID); case DATE_DIALOG_ID: return new DatePickerDialog(this, mDateSetListener, @@ -105,7 +104,8 @@ public class DateWidgets1 extends Activity { @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { - case TIME_DIALOG_ID: + case TIME_12_DIALOG_ID: + case TIME_24_DIALOG_ID: ((TimePickerDialog) dialog).updateTime(mHour, mMinute); break; case DATE_DIALOG_ID: diff --git a/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList1.java b/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList1.java index bda366beb..ef8c03c66 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList1.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList1.java @@ -118,7 +118,9 @@ public class ExpandableList1 extends ExpandableListActivity { // Center the text vertically textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); // Set the text starting position - textView.setPadding(36, 0, 0, 0); + textView.setPaddingRelative(36, 0, 0, 0); + // Set the text alignment + textView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); return textView; } diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java b/samples/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java index ce7c749df..10b2f9a72 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java @@ -142,7 +142,7 @@ public class GameControllerInput extends Activity public boolean dispatchGenericMotionEvent(MotionEvent event) { // Check that the event came from a joystick since a generic motion event // could be almost anything. - if (isJoystick(event.getSource()) + if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { // Update device state for visualization and logging. InputDeviceState state = getInputDeviceState(event.getDeviceId()); @@ -195,10 +195,6 @@ public class GameControllerInput extends Activity } } - private static boolean isJoystick(int source) { - return (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0; - } - /** * Tracks the state of joystick axes and game controller buttons for a particular * input device for diagnostic purposes. @@ -215,7 +211,7 @@ public class GameControllerInput extends Activity int numAxes = 0; final List<MotionRange> ranges = device.getMotionRanges(); for (MotionRange range : ranges) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) { numAxes += 1; } } @@ -224,7 +220,7 @@ public class GameControllerInput extends Activity mAxisValues = new float[numAxes]; int i = 0; for (MotionRange range : ranges) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) { mAxes[i++] = range.getAxis(); } } diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GameView.java b/samples/ApiDemos/src/com/example/android/apis/view/GameView.java index 2904147ef..d7f039bdd 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/GameView.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/GameView.java @@ -209,7 +209,7 @@ public class GameView extends View { // Check that the event came from a joystick since a generic motion event // could be almost anything. - if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 + if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { // Cache the most recently obtained device information. // The device information may change over time but it can be @@ -771,4 +771,4 @@ public class GameView extends View { return 0.25f; } } -}
\ No newline at end of file +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java index f4318e454..3221fa0d7 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java @@ -16,13 +16,13 @@ package com.example.android.apis.view; -import com.example.android.apis.R; - import android.app.Activity; import android.os.Bundle; +import com.example.android.apis.R; /** - * A simple form, showing use of the GridLayout API from XML. + * Demonstrates using GridLayout to build the same "Simple Form" as in the + * LinearLayout and RelativeLayout demos. */ public class GridLayout1 extends Activity { protected void onCreate(Bundle savedInstanceState) { diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java new file mode 100644 index 000000000..2ad09f497 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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.example.android.apis.view; + +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; + +/** + * A form, showing use of the GridLayout API from XML. + */ +public class GridLayout2 extends Activity { + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.grid_layout_2); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout3.java index 70de1ac39..48a55467a 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout3.java @@ -18,6 +18,7 @@ package com.example.android.apis.view; import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.os.Bundle; import android.view.View; @@ -25,81 +26,84 @@ import android.widget.*; import static android.text.InputType.*; import static android.widget.GridLayout.*; +import static android.widget.GridLayout.LayoutParams; /** - * A simple form, showing use of the GridLayout API. + * A form, showing use of the GridLayout API. Here we demonstrate use of the row/column order + * preserved property which allows rows and or columns to pass over each other when needed. + * The two buttons in the bottom right corner need to be separated from the other UI elements. + * This can either be done by separating rows or separating columns - but we don't need + * to do both and may only have enough space to do one or the other. */ -public class GridLayout0 extends Activity { - +public class GridLayout3 extends Activity { public static View create(Context context) { GridLayout p = new GridLayout(context); p.setUseDefaultMargins(true); p.setAlignmentMode(ALIGN_BOUNDS); - p.setRowOrderPreserved(false); + Configuration configuration = context.getResources().getConfiguration(); + if ((configuration.orientation == Configuration.ORIENTATION_PORTRAIT)) { + p.setColumnOrderPreserved(false); + } else { + p.setRowOrderPreserved(false); + } - Spec row1 = spec(0); - Spec row2 = spec(1); - Spec row3 = spec(2, BASELINE); - Spec row4 = spec(3, BASELINE); - Spec row5 = spec(2, 3, FILL); // allow the last two rows to overlap the middle two - Spec row6 = spec(5); - Spec row7 = spec(6); + Spec titleRow = spec(0); + Spec introRow = spec(1); + Spec emailRow = spec(2, BASELINE); + Spec passwordRow = spec(3, BASELINE); + Spec button1Row = spec(5); + Spec button2Row = spec(6); - Spec col1a = spec(0, 4, CENTER); - Spec col1b = spec(0, 4, LEFT); - Spec col1c = spec(0, RIGHT); - Spec col2 = spec(1, LEFT); - Spec col3 = spec(2, FILL); - Spec col4a = spec(3); - Spec col4b = spec(3, FILL); + Spec centerInAllColumns = spec(0, 4, CENTER); + Spec leftAlignInAllColumns = spec(0, 4, LEFT); + Spec labelColumn = spec(0, RIGHT); + Spec fieldColumn = spec(1, LEFT); + Spec defineLastColumn = spec(3); + Spec fillLastColumn = spec(3, FILL); { TextView c = new TextView(context); c.setTextSize(32); c.setText("Email setup"); - p.addView(c, new LayoutParams(row1, col1a)); + p.addView(c, new LayoutParams(titleRow, centerInAllColumns)); } { TextView c = new TextView(context); c.setTextSize(16); - c.setText("You can configure email in just a few steps:"); - p.addView(c, new LayoutParams(row2, col1b)); + c.setText("You can configure email in a few simple steps:"); + p.addView(c, new LayoutParams(introRow, leftAlignInAllColumns)); } { TextView c = new TextView(context); c.setText("Email address:"); - p.addView(c, new LayoutParams(row3, col1c)); + p.addView(c, new LayoutParams(emailRow, labelColumn)); } { EditText c = new EditText(context); c.setEms(10); c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - p.addView(c, new LayoutParams(row3, col2)); + p.addView(c, new LayoutParams(emailRow, fieldColumn)); } { TextView c = new TextView(context); c.setText("Password:"); - p.addView(c, new LayoutParams(row4, col1c)); + p.addView(c, new LayoutParams(passwordRow, labelColumn)); } { TextView c = new EditText(context); c.setEms(8); c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD); - p.addView(c, new LayoutParams(row4, col2)); - } - { - Space c = new Space(context); - p.addView(c, new LayoutParams(row5, col3)); + p.addView(c, new LayoutParams(passwordRow, fieldColumn)); } { Button c = new Button(context); c.setText("Manual setup"); - p.addView(c, new LayoutParams(row6, col4a)); + p.addView(c, new LayoutParams(button1Row, defineLastColumn)); } { Button c = new Button(context); c.setText("Next"); - p.addView(c, new LayoutParams(row7, col4b)); + p.addView(c, new LayoutParams(button2Row, fillLastColumn)); } return p; diff --git a/samples/ApiDemos/src/com/example/android/apis/view/OverscanActivity.java b/samples/ApiDemos/src/com/example/android/apis/view/SystemUIModes.java index 309799270..8197e05da 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/OverscanActivity.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/SystemUIModes.java @@ -27,12 +27,10 @@ import android.os.Bundle; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.ActionMode; -import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; @@ -51,10 +49,10 @@ import com.example.android.apis.R; * the system decor, in order to better focus the user's attention or use available screen real * estate on the task at hand. */ -public class OverscanActivity extends Activity +public class SystemUIModes extends Activity implements OnQueryTextListener, ActionBar.TabListener { public static class IV extends ImageView implements View.OnSystemUiVisibilityChangeListener { - private OverscanActivity mActivity; + private SystemUIModes mActivity; private ActionMode mActionMode; public IV(Context context) { super(context); @@ -62,7 +60,7 @@ public class OverscanActivity extends Activity public IV(Context context, AttributeSet attrs) { super(context, attrs); } - public void setActivity(OverscanActivity act) { + public void setActivity(SystemUIModes act) { setOnSystemUiVisibilityChangeListener(this); mActivity = act; } @@ -126,6 +124,42 @@ public class OverscanActivity extends Activity win.setAttributes(winParams); } + private void setOverscan(boolean on) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + final int bits = WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN; + if (on) { + winParams.flags |= bits; + } else { + winParams.flags &= ~bits; + } + win.setAttributes(winParams); + } + + private void setTranslucentStatus(boolean on) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; + if (on) { + winParams.flags |= bits; + } else { + winParams.flags &= ~bits; + } + win.setAttributes(winParams); + } + + private void setTranslucentNavigation(boolean on) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; + if (on) { + winParams.flags |= bits; + } else { + winParams.flags &= ~bits; + } + win.setAttributes(winParams); + } + private String getDisplaySize() { DisplayMetrics dm = getResources().getDisplayMetrics(); return String.format("DisplayMetrics = (%d x %d)", dm.widthPixels, dm.heightPixels); @@ -141,24 +175,23 @@ public class OverscanActivity extends Activity static int TOAST_LENGTH = 500; IV mImage; - CheckBox[] mCheckControls = new CheckBox[6]; + CheckBox[] mCheckControls = new CheckBox[8]; int[] mCheckFlags = new int[] { View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_FULLSCREEN, View.SYSTEM_UI_FLAG_HIDE_NAVIGATION, + View.SYSTEM_UI_FLAG_IMMERSIVE, View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY, View.SYSTEM_UI_FLAG_LAYOUT_STABLE, View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION }; TextView mMetricsText; - public OverscanActivity() { + public SystemUIModes() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - - setContentView(R.layout.overscan); + setContentView(R.layout.system_ui_modes); mImage = (IV) findViewById(R.id.image); mImage.setActivity(this); @@ -171,9 +204,11 @@ public class OverscanActivity extends Activity mCheckControls[0] = (CheckBox) findViewById(R.id.modeLowProfile); mCheckControls[1] = (CheckBox) findViewById(R.id.modeFullscreen); mCheckControls[2] = (CheckBox) findViewById(R.id.modeHideNavigation); - mCheckControls[3] = (CheckBox) findViewById(R.id.layoutStable); - mCheckControls[4] = (CheckBox) findViewById(R.id.layoutFullscreen); - mCheckControls[5] = (CheckBox) findViewById(R.id.layoutHideNavigation); + mCheckControls[3] = (CheckBox) findViewById(R.id.modeImmersive); + mCheckControls[4] = (CheckBox) findViewById(R.id.modeImmersiveSticky); + mCheckControls[5] = (CheckBox) findViewById(R.id.layoutStable); + mCheckControls[6] = (CheckBox) findViewById(R.id.layoutFullscreen); + mCheckControls[7] = (CheckBox) findViewById(R.id.layoutHideNavigation); for (int i=0; i<mCheckControls.length; i++) { mCheckControls[i].setOnCheckedChangeListener(checkChangeListener); } @@ -185,6 +220,30 @@ public class OverscanActivity extends Activity } } ); + ((CheckBox) findViewById(R.id.windowOverscan)).setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setOverscan(isChecked); + } + } + ); + ((CheckBox) findViewById(R.id.windowTranslucentStatus)).setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setTranslucentStatus(isChecked); + } + } + ); + ((CheckBox) findViewById(R.id.windowTranslucentNav)).setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setTranslucentNavigation(isChecked); + } + } + ); ((CheckBox) findViewById(R.id.windowHideActionBar)).setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @Override diff --git a/samples/ApiDemos/src/com/example/android/apis/view/SystemUIModesOverlay.java b/samples/ApiDemos/src/com/example/android/apis/view/SystemUIModesOverlay.java new file mode 100644 index 000000000..73eadb1dd --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/SystemUIModesOverlay.java @@ -0,0 +1,31 @@ +/* + * 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.example.android.apis.view; + +import android.os.Bundle; +import android.view.Window; + +/** + * Version of demo that uses the action bar in overlay mode. + */ +public class SystemUIModesOverlay extends SystemUIModes { + @Override + public void onCreate(Bundle savedInstanceState) { + getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + super.onCreate(savedInstanceState); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java b/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java new file mode 100644 index 000000000..f2b85cedc --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.apis.view; + +// Need the following import to get access to the app resources, since this +// class is in a sub-package. +import android.text.Html; +import android.widget.TextClock; +import com.example.android.apis.R; + +import android.app.Activity; +import android.os.Bundle; + +/** + * Variants of {@link TextClock}. + */ +public class TextClockDemo extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.textclock); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/TranslucentBarsActivity.java b/samples/ApiDemos/src/com/example/android/apis/view/TranslucentBarsActivity.java new file mode 100644 index 000000000..98c11d1a6 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/TranslucentBarsActivity.java @@ -0,0 +1,36 @@ +/* + * 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.example.android.apis.view; + +import android.app.Activity; +import android.os.Bundle; +import com.example.android.apis.R; + +/** + * This activity demonstrates a simple implementation of displaying + * content with translucent system UI bars. + */ +public class TranslucentBarsActivity extends Activity { + public TranslucentBarsActivity() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.translucent_bars); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java b/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java index e5333d705..3bee59460 100644 --- a/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java +++ b/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java @@ -167,7 +167,7 @@ public class VideoPlayerActivity extends Activity h.removeCallbacks(mNavHider); if (!mMenusOpen && !mPaused) { // If the menus are open or play is paused, we will not auto-hide. - h.postDelayed(mNavHider, 1500); + h.postDelayed(mNavHider, 3000); } } } diff --git a/samples/ApiDemos/src/com/example/android/apis/view/WindowFocusObserver.java b/samples/ApiDemos/src/com/example/android/apis/view/WindowFocusObserver.java new file mode 100644 index 000000000..83c47288a --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/view/WindowFocusObserver.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 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.example.android.apis.view; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.WindowId; +import android.view.WindowId.FocusObserver; +import android.widget.SearchView; +import android.widget.ShareActionProvider; +import android.widget.TextView; +import android.widget.Toast; +import com.example.android.apis.R; + +public class WindowFocusObserver extends Activity implements SearchView.OnQueryTextListener { + TextView mState; + + final FocusObserver mObserver = new WindowId.FocusObserver() { + + @Override + public void onFocusGained(WindowId token) { + mState.setText("Gained focus"); + } + + @Override + public void onFocusLost(WindowId token) { + mState.setText("Lost focus"); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.window_focus_observer); + mState = (TextView)findViewById(R.id.focus_state); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.content_actions, menu); + SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); + searchView.setOnQueryTextListener(this); + + // Set file with share history to the provider and set the share intent. + MenuItem actionItem = menu.findItem(R.id.menu_item_share_action_provider_action_bar); + ShareActionProvider actionProvider = (ShareActionProvider) actionItem.getActionProvider(); + actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME); + // Note that you can set/change the intent any time, + // say when the user has selected an image. + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("image/*"); + Uri uri = Uri.fromFile(getFileStreamPath("shared.png")); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + actionProvider.setShareIntent(shareIntent); + return true; + } + + public void onSort(MenuItem item) { + } + + @Override + public boolean onQueryTextChange(String newText) { + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + Toast.makeText(this, "Searching for: " + query + "...", Toast.LENGTH_SHORT).show(); + return true; + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + WindowId token = getWindow().getDecorView().getWindowId(); + token.registerFocusObserver(mObserver); + mState.setText(token.isFocused() ? "Focused" : "Not focused"); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getWindow().getDecorView().getWindowId().unregisterFocusObserver(mObserver); + } +} |
