au モトローラ XOOM で Android 3.2 バージョンアップ認識せず

昨年の12月に公開された XOOM の Android 3.2 をそろそろマイXOOMへも適用しようと公式サイトの手順通りにやってみましたが、うんともすんともダウンロード画面がでてこず軽くはまりました。

http://www.au.kddi.com/seihin/ichiran/smartphone/up_date/xoom/up_date_20111215.html

結局、端末初期化したら認識するようになりました。

http://motorola-global-portal-jp.custhelp.com/app/answers/detail/a_id/65091/~/motorola-xoom---%E5%88%9D%E6%9C%9F%E5%8C%96

ExpandableListFragment

ネットに転がっている ExpandableListFragment が例外で落ちるとか期待動作が異なるとかだったので、修正するついでに手を加えてみた。以下変更点

  • ListFragment と同様のコンテンツ読み込み中のくるくるを実装
  • デリゲートする(expandable listのフラグメントとしての)意味から onListItemClick や setSelection を削除、ExpandableListActivityと同様に onChildClick や setSelectedGroup、setSelectedChild などの実装を追加
  • ExpandableListView.OnGroupClickListener のデフォルト実装を追加
  • Android 1.6/2.1 で onGroupExpand 時に頭出しスクロールしない問題の対応を追加
/*
 * 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.orangesignal.android.support.v4.app;

import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * An fragment that displays an expandable list of items by binding to a data
 * source implementing the ExpandableListAdapter, and exposes event handlers
 * when the user selects an item.
 */
public class ExpandableListFragment extends Fragment implements
	ExpandableListView.OnChildClickListener,
	ExpandableListView.OnGroupCollapseListener,
	ExpandableListView.OnGroupExpandListener,
	ExpandableListView.OnGroupClickListener {

	static final int INTERNAL_EMPTY_ID = 0x00ff0001;
	static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
	static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;

	private final Handler mHandler = new Handler();

	private final Runnable mRequestFocus = new Runnable() {
		@Override
		public void run() {
			mList.focusableViewAvailable(mList);
		}
	};

	ExpandableListAdapter mAdapter;
	ExpandableListView mList;
	View mEmptyView;
	TextView mStandardEmptyView;
	View mProgressContainer;
	View mListContainer;
	CharSequence mEmptyText;
	boolean mListShown;

	public ExpandableListFragment() {
	}

	/**
	 * Provide default implementation to return a expandable list view. Subclasses
	 * can override to replace with their own layout. If doing so, the returned
	 * view hierarchy <em>must</em> have a ExpandableListView whose id
	 * is {@link android.R.id#list android.R.id.list} and can optionally
	 * have a sibling view id {@link android.R.id#empty android.R.id.empty}
	 * that is to be shown when the list is empty.
	 *
	 * <p>If you are overriding this method with your own custom content,
	 * consider including the standard layout {@link android.R.layout#list_content}
	 * in your layout file, so that you continue to retain all of the standard
	 * behavior of ExpandableListFragment.
	 * In particular, this is currently the only way to have the built-in indeterminant progress state be shown.
	 *
	 * @param inflater The LayoutInflater object that can be used to inflate any views in the fragment,
	 * @param container If non-null, this is the parent view that the fragment's UI should be attached to. The fragment should not add the view itself, but this can be used to generate the LayoutParams of the view.
	 * @param savedInstanceState If non-null, this fragment is being re-constructed from a previous saved state as given here.
	 * @return Return the View for the fragment's UI, or null.
	 */
	@Override
	public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
		final Context context = getActivity();

		final FrameLayout root = new FrameLayout(context);

		// ------------------------------------------------------------------

		final LinearLayout pframe = new LinearLayout(context);
		pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
		pframe.setOrientation(LinearLayout.VERTICAL);
		pframe.setVisibility(View.GONE);
		pframe.setGravity(Gravity.CENTER);

		final ProgressBar progress = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
		pframe.addView(progress, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

		root.addView(pframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));

		// ------------------------------------------------------------------

		final FrameLayout lframe = new FrameLayout(context);
		lframe.setId(INTERNAL_LIST_CONTAINER_ID);

		final TextView tv = new TextView(getActivity());
		tv.setId(INTERNAL_EMPTY_ID);
		tv.setGravity(Gravity.CENTER);
		lframe.addView(tv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));

		final ExpandableListView lv = new ExpandableListView(getActivity());
		lv.setId(android.R.id.list);
		lv.setDrawSelectorOnTop(false);
		lframe.addView(lv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));

		root.addView(lframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));

		// ------------------------------------------------------------------

		root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));

		return root;
	}

	/**
	 * Attach to expandable list view once the view hierarchy has been created.
	 *
	 * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
	 * @param savedInstanceState If non-null, this fragment is being re-constructed from a previous saved state as given here.
	 */
	@Override
	public void onViewCreated(final View view, final Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		ensureList();
	}

	/**
	 * Detach from expandable list view.
	 */
	@Override
	public void onDestroyView() {
		mHandler.removeCallbacks(mRequestFocus);
		mList = null;
		mListShown = false;
		mEmptyView = mProgressContainer = mListContainer = null;
		mStandardEmptyView = null;
		super.onDestroyView();
	}

	@Override
	public boolean onGroupClick(final ExpandableListView parent, final View v, final int groupPosition, final long id) {
		return false;
	}

	private static final int FROYO = 8;

	/**
	 * Override this for receiving callbacks when a group has been expanded.
	 *
	 * @param groupPosition The group position that was expanded
	 */
	@Override
	public void onGroupExpand(final int groupPosition) {
		if (Build.VERSION.SDK_INT < FROYO) {
			mList.setSelectedGroup(groupPosition);
		}
	}

	/**
	 * Override this for receiving callbacks when a group has been collapsed.
	 *
	 * @param groupPosition The group position that was collapsed
	 */
	@Override
	public void onGroupCollapse(final int groupPosition) {
	}

	/**
	 * Override this for receiving callbacks when a child has been clicked.
	 * Callback method to be invoked when a child in this expandable list has been clicked.
	 *
	 * @param parent The ExpandableListView where the click happened
	 * @param v The view within the expandable list/ListView that was clicked
	 * @param groupPosition The group position that contains the child that was clicked
	 * @param childPosition The child position within the group
	 * @param id The row id of the child that was clicked
	 */
	@Override
	public boolean onChildClick(final ExpandableListView parent, final View v, final int groupPosition, final int childPosition, final long id) {
		return false;
	}

	/**
	 * Provide the adapter for the expandable list.
	 */
	public void setListAdapter(final ExpandableListAdapter adapter) {
		final boolean hadAdapter = mAdapter != null;
		mAdapter = adapter;
		if (mList != null) {
			mList.setAdapter(adapter);
			if (!mListShown && !hadAdapter) {
				// The list was hidden, and previously didn't have an
				// adapter.  It is now time to show it.
				setListShown(true, getView().getWindowToken() != null);
			}
		}
	}

	/**
	 * Sets the selection to the specified group.
	 *
	 * @param groupPosition The position of the group that should be selected.
	 */
	public void setSelectedGroup(final int groupPosition) {
		ensureList();
		mList.setSelectedGroup(groupPosition);
	}

	/**
	 * Sets the selection to the specified child.
	 * If the child is in a collapsed group, the group will only be expanded and child subsequently selected if shouldExpandGroup is set to true, otherwise the method will return false.
	 *
	 * @param groupPosition The position of the group that contains the child.
	 * @param childPosition The position of the child within the group.
	 * @param shouldExpandGroup Whether the child's group should be expanded if it is collapsed.
	 * @return Whether the selection was successfully set on the child.
	 */
	public boolean setSelectedChild(final int groupPosition, final int childPosition, final boolean shouldExpandGroup) {
		ensureList();
		return mList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup);
	}

	/**
	 * Gets the position (in packed position representation) of the currently selected group or child.
	 * Use getPackedPositionType(long), getPackedPositionGroup(long), and getPackedPositionChild(long) to unpack the returned packed position.
	 *
	 * @return A packed position representation containing the currently selected group or child's position and type.
	 */
	public long getSelectedPosition() {
		ensureList();
		return mList.getSelectedPosition();
	}

	/**
	 * Gets the ID of the currently selected group or child.
	 *
	 * @return The ID of the currently selected group or child.
	 */
	public long getSelectedId() {
		ensureList();
		return mList.getSelectedId();
	}

	/**
	 * Get the activity's expandable list view widget.
	 */
	public ExpandableListView getExpandableListView() {
		ensureList();
		return mList;
	}

	/**
	 * The default content for a ExpandableListFragment has a TextView that can be shown when the list is empty.
	 * If you would like to have it shown, call this method to supply the text it should use.
	 */
	public void setEmptyText(final CharSequence text) {
		ensureList();
		if (mStandardEmptyView == null) {
			throw new IllegalStateException("Can't be used with a custom content view");
		}
		mStandardEmptyView.setText(text);
		if (mEmptyText == null) {
			mList.setEmptyView(mStandardEmptyView);
		}
		mEmptyText = text;
	}

	/**
	 * Control whether the list is being displayed.
	 * You can make it not displayed if you are waiting for the initial data to show in it.
	 * During this time an indeterminant progress indicator will be shown instead.
	 *
	 * <p>Applications do not normally need to use this themselves.  The default
	 * behavior of ListFragment is to start with the list not being shown, only
	 * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}.
	 * If the list at that point had not been shown, when it does get shown
	 * it will be do without the user ever seeing the hidden state.
	 *
	 * @param shown If true, the list view is shown; if false, the progress indicator. The initial value is true.
	 */
	public void setListShown(final boolean shown) {
		setListShown(shown, true);
	}

	/**
	 * Like {@link #setListShown(boolean)}, but no animation is used when transitioning from the previous state.
	 */
	public void setListShownNoAnimation(final boolean shown) {
		setListShown(shown, false);
	}

	/**
	 * Control whether the list is being displayed.
	 * You can make it not displayed if you are waiting for the initial data to show in it.
	 * During this time an indeterminant progress indicator will be shown instead.
	 *
	 * @param shown If true, the list view is shown; if false, the progress indicator. The initial value is true.
	 * @param animate If true, an animation will be used to transition to the new state.
	 */
	private void setListShown(final boolean shown, final boolean animate) {
		ensureList();
		if (mProgressContainer == null) {
			throw new IllegalStateException("Can't be used with a custom content view");
		}
		if (mListShown == shown) {
			return;
		}
		mListShown = shown;
		if (shown) {
			if (animate) {
				mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out));
				mListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in));
			} else {
				mProgressContainer.clearAnimation();
				mListContainer.clearAnimation();
			}
			mProgressContainer.setVisibility(View.GONE);
			mListContainer.setVisibility(View.VISIBLE);
		} else {
			if (animate) {
				mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in));
				mListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out));
			} else {
				mProgressContainer.clearAnimation();
				mListContainer.clearAnimation();
			}
			mProgressContainer.setVisibility(View.VISIBLE);
			mListContainer.setVisibility(View.GONE);
		}
    }

	/**
	 * Get the ExpandableListAdapter associated with this activity's ExpandableListView.
	 */
	public ExpandableListAdapter getExpandableListAdapter() {
		return mAdapter;
	}

	private void ensureList() {
		if (mList != null) {
			return;
		}
		final View root = getView();
		if (root == null) {
			throw new IllegalStateException("Content view not yet created");
		}
		if (root instanceof ExpandableListView) {
			mList = (ExpandableListView) root;
		} else {
			mStandardEmptyView = (TextView) root.findViewById(INTERNAL_EMPTY_ID);
			if (mStandardEmptyView == null) {
				mEmptyView = root.findViewById(android.R.id.empty);
			} else {
				mStandardEmptyView.setVisibility(View.GONE);
			}
			mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
			mListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
			View rawListView = root.findViewById(android.R.id.list);
			if (!(rawListView instanceof ExpandableListView)) {
				if (rawListView == null) {
					throw new RuntimeException("Your content must have a ExpandableListView whose id attribute is 'android.R.id.list'");
				}
				throw new RuntimeException("Content has view with id attribute 'android.R.id.list' that is not a ExpandableListView class");
			}
			mList = (ExpandableListView) rawListView;
			if (mEmptyView != null) {
				mList.setEmptyView(mEmptyView);
			} else if (mEmptyText != null) {
				mStandardEmptyView.setText(mEmptyText);
				mList.setEmptyView(mStandardEmptyView);
			}
		}
		mListShown = true;
//		mList.setOnCreateContextMenuListener(this);
		mList.setOnChildClickListener(this);
		mList.setOnGroupCollapseListener(this);
		mList.setOnGroupExpandListener(this);
		mList.setOnGroupClickListener(this);
		if (mAdapter != null) {
			final ExpandableListAdapter adapter = mAdapter;
			mAdapter = null;
			setListAdapter(adapter);
		} else {
			// We are starting without an adapter, so assume we won't
			// have our data right away and start with the progress indicator.
			if (mProgressContainer != null) {
				setListShown(false, false);
			}
		}
		mHandler.post(mRequestFocus);
	}

}

Android 3.2 でましたね

みなさんこんにちは。久々のブログ更新です。
死にブログになっておりますが、企画・開発せていただいた 某Androidタブレットアプリを無事リリースして連休まったりしております。

今朝方何気に Android Developer サイトを見ていたら「Android 3.2 is here!」と記載が・・・でちゃいましたね。
追加や変更、非推奨となったクラス群などちょっと注意しておいた方がよいものばかりですが、個人的には Fragment の SavedInstanceState が標準で提供されるようになったのがとても◎
この機能はバックスタックでフラグメントを管理せずに自分で管理したい場合など特に必要となってきます。

それにしてもAndroid 3.x 系は 4.x 系へのつなぎなのでしょうかね。
様々なアプリのケースを考えると Android 3.2 で提供されている機能レベルが実質最低ラインではないかと感じます。
ということでまだまだ過渡期なのでしょうね。

ひさびさにちょっとゆっくり

先日、確定申告も無事に済ませて、3月分のもろもろの事務処理も一通り前倒しで準備できたので
今夜は久々にちょっとゆっくりしています。

来週からはもっと忙しくなりそうですがエキサイティングな日々となりそうです。
その前にまずは伸ばし放題になってしまった髪を切りにいくか…

確定申告シーズン

到来ですね。皆様もうお済みでしょうか?

僕もそろそろ申告行くための準備をしているのですが、昨夜に事務処理用のマスターPCが煙を出してお亡くなりになるというアクシデントが…先月の〆処理も土日に終わるかどうか怪しい…何というタイミングの良さだろうかと…大事ではあるのですが、実はこの状況を少しだけ楽しんでいるところもあります。まあそうでないとやってられないからね^^;

早くもろもろを終わらせてイチゴ狩りにでも行きたい。じゃらん観光ガイドアプリをながめて気分だけは既に現地へ飛んでいます。
http://www.jalan.net/jalan/doc/howto/android_kankou.html

Android でアプリケーション開始時と終了時にごにょごにょする

怒濤のリリースラッシュも無事に終え、先日はまっていたエコ化+高速化も乗り越え(最後はやはり skia との格闘戦でした;;)てバージョンアップリリースの目処が立ってきたので今日は少しネットを見ています。

Android の static 変数はヤバい
http://amay077.posterous.com/android-static

※だれか Activity じゃなくて ”アプリケーション" が終了するタイミングを補足する方法、教えてください

コメントを見ると既に解決されたっぽいですね。なので話をちょこっとずらして、アプリの開始と終了時に何かしらごにょごにょする方法をご紹介。

アプリの開始と終了時に何かしらごにょごにょするには、android.app.Application を継承したクラスで行うことができます。以下は、アプリの開始/終了時に GoogleAnalyticsTracker を開始/終了させるシナリオの例です。(googleAnalytics.dispatch は通信時にやる前提です)

package com.orangesignal.android.apps.hoge;

public final class Hoge extends android.app.Application {

    /**
     * 共通 {@link GoogleAnalyticsTracker} オブジェクトのインスタンスです。
     */
    public static final GoogleAnalyticsTracker googleAnalytics = GoogleAnalyticsTracker.getInstance();

    @Override
    public void onCreate() {
        super.onCreate();
        googleAnalytics.start("UA-XXXXXXXX-X", this);  // TODO: Web プロパティID は自分のに書き換える
    }

    @Override
    public void onTerminate() {
        googleAnalytics.stop();
        super.onTerminate();
    }

}

そして、例によって AndroidManifest.xml へ使用するアプリケーションクラスを明示的に定義します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.orangesignal.android.apps.hoge" android:versionCode="1" android:versionName="1.0.0">

  <application android:debuggable="true"
               android:icon="@drawable/icon"
               android:label="@string/app_name"
               android:name=".Hoge">

    (中略…ここにサービスやアクティビティなどを定義します)

  </application>

</manifest>

※何だか備忘録的内容になってしまいましたがこの記事がどなたかのお役に立てれば幸いです。

Android 2.2 + ListView + ImageView + AsyncTask + ThreadSafeClientConnManager + BitmapFactory.Options ではまる

表題の通りなのですが、ListView + ImageView で Web 画像表示する機能についてエコ(低メモリ)+高速化しようとして、10年ぶりくらいにはまりました。
当初はてっきり、また skia の問題か?と思っていたのですがどうやら違ったようです。久々に log 張りまくって調査して原因は十中八九 AsyncTask のようだというところまできたので(最初から git へ行ってソースを読めばこれに1日費やす必要はなかったかもしれない)、AsyncTask をやめて Runnable + android.os.Handler として再実装、んでクラス抽出したら期待動作になりました。
明日は実機で一日検証ですな。