Merge "add an app to generate localized text bitmaps needed for recovery" into jb-mr1-dev
This commit is contained in:
12
tools/recovery_l10n/Android.mk
Normal file
12
tools/recovery_l10n/Android.mk
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_PACKAGE_NAME := RecoveryLocalizer
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
18
tools/recovery_l10n/AndroidManifest.xml
Normal file
18
tools/recovery_l10n/AndroidManifest.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.recovery_l10n">
|
||||
|
||||
<application android:label="Recovery Localizer">
|
||||
<activity android:name="Main"
|
||||
android:label="Recovery Localizer">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
|
||||
30
tools/recovery_l10n/res/layout/main.xml
Normal file
30
tools/recovery_l10n/res/layout/main.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<Spinner android:id="@+id/which"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
<Button android:id="@+id/go"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/go"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#ffffffff"
|
||||
android:background="#ff000000"
|
||||
android:maxWidth="480px"
|
||||
/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
9
tools/recovery_l10n/res/values-de/strings.xml
Normal file
9
tools/recovery_l10n/res/values-de/strings.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="recovery_installing">Systemupdate wird installiert\u2026</string>
|
||||
<string name="recovery_erasing">Löschen\u2026</string>
|
||||
<string name="recovery_no_command">Kein Befehl.</string>
|
||||
<string name="recovery_error">Fehler!</string>
|
||||
|
||||
</resources>
|
||||
34
tools/recovery_l10n/res/values/strings.xml
Normal file
34
tools/recovery_l10n/res/values/strings.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Do not translate. -->
|
||||
<string translatable="false" name="go">Go</string>
|
||||
|
||||
<!-- Do not translate. -->
|
||||
<string-array translatable="false" name="string_options">
|
||||
<item>installing</item>
|
||||
<item>erasing</item>
|
||||
<item>no_command</item>
|
||||
<item>error</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Displayed on the screen beneath the animated android while the
|
||||
system is installing an update. [CHAR LIMIT=60] -->
|
||||
<string name="recovery_installing">Installing system update\u2026</string>
|
||||
|
||||
<!-- Displayed on the screen beneath the animated android while the
|
||||
system is erasing a partition (either a data wipe aka "factory
|
||||
reset", or a cache wipe). [CHAR LIMIT=60] -->
|
||||
<string name="recovery_erasing">Erasing\u2026</string>
|
||||
|
||||
<!-- Displayed on the screen when the user has gotten into recovery
|
||||
mode without a command to run. Will not normally happen, but
|
||||
users (especially developers) may boot into recovery mode
|
||||
manually via special key combinations. [CHAR LIMIT=60] -->
|
||||
<string name="recovery_no_command">No command.</string>
|
||||
|
||||
<!-- Displayed on the triangle-! screen when a system update
|
||||
installation or data wipe procedure encounters an error. [CHAR
|
||||
LIMIT=60] -->
|
||||
<string name="recovery_error">Error!</string>
|
||||
|
||||
</resources>
|
||||
269
tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
Normal file
269
tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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.android.recovery_l10n;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This activity assists in generating the specially-formatted bitmaps
|
||||
* of text needed for recovery's localized text display. Each image
|
||||
* contains all the translations of a single string; above each
|
||||
* translation is a "header row" that encodes that subimage's width,
|
||||
* height, and locale using pixel values.
|
||||
*
|
||||
* To use this app to generate new translations:
|
||||
*
|
||||
* - Update the string resources in res/values-*
|
||||
*
|
||||
* - Update the list of desired locales (mLocales) below. Locales
|
||||
* should be ordered by decreasing popularity (ie, the most
|
||||
* commonly-used one first).
|
||||
*
|
||||
* - Build and run the app. Select the string you want to
|
||||
* translate, and press the "Go" button.
|
||||
*
|
||||
* - Wait for it to finish cycling through all the strings, then
|
||||
* pull /data/data/com.android.recovery_l10n/files/text-out.png
|
||||
* from the device.
|
||||
*
|
||||
* - "pngcrush -c 0 text-out.png output.png"
|
||||
*
|
||||
* - Put output.png in bootable/recovery/res/images/ (renamed
|
||||
* appropriately).
|
||||
*
|
||||
* Recovery expects 8-bit 1-channel images (white text on black
|
||||
* background). pngcrush -c 0 will convert the output of this program
|
||||
* to such an image. If you use any other image handling tools,
|
||||
* remember that they must be lossless to preserve the exact values of
|
||||
* pixels in the header rows; don't convert them to jpeg or anything.
|
||||
*/
|
||||
|
||||
public class Main extends Activity {
|
||||
private static final String TAG = "RecoveryL10N";
|
||||
|
||||
final Locale[] mLocales = new Locale[] { Locale.US, Locale.GERMANY };
|
||||
|
||||
HashMap<Locale, Bitmap> savedBitmaps;
|
||||
TextView mText;
|
||||
int mStringId = R.string.recovery_installing;
|
||||
|
||||
public class TextCapture implements Runnable {
|
||||
private Locale nextLocale;
|
||||
private Locale thisLocale;
|
||||
private Runnable next;
|
||||
|
||||
TextCapture(Locale thisLocale, Locale nextLocale, Runnable next) {
|
||||
this.nextLocale = nextLocale;
|
||||
this.thisLocale = thisLocale;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Bitmap b = mText.getDrawingCache();
|
||||
savedBitmaps.put(thisLocale, b.copy(Bitmap.Config.ARGB_8888, false));
|
||||
|
||||
if (nextLocale != null) {
|
||||
switchTo(nextLocale);
|
||||
}
|
||||
|
||||
if (next != null) {
|
||||
mText.postDelayed(next, 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void switchTo(Locale locale) {
|
||||
Resources standardResources = getResources();
|
||||
AssetManager assets = standardResources.getAssets();
|
||||
DisplayMetrics metrics = standardResources.getDisplayMetrics();
|
||||
Configuration config = new Configuration(standardResources.getConfiguration());
|
||||
config.locale = locale;
|
||||
Resources defaultResources = new Resources(assets, metrics, config);
|
||||
|
||||
mText.setText(mStringId);
|
||||
|
||||
mText.setDrawingCacheEnabled(false);
|
||||
mText.setDrawingCacheEnabled(true);
|
||||
mText.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstance) {
|
||||
super.onCreate(savedInstance);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
savedBitmaps = new HashMap<Locale, Bitmap>();
|
||||
|
||||
Spinner spinner = (Spinner) findViewById(R.id.which);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
||||
this, R.array.string_options, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView parent, View view,
|
||||
int pos, long id) {
|
||||
switch (pos) {
|
||||
case 0: mStringId = R.string.recovery_installing; break;
|
||||
case 1: mStringId = R.string.recovery_erasing; break;
|
||||
case 2: mStringId = R.string.recovery_no_command; break;
|
||||
case 3: mStringId = R.string.recovery_error; break;
|
||||
}
|
||||
}
|
||||
@Override public void onNothingSelected(AdapterView parent) { }
|
||||
});
|
||||
|
||||
mText = (TextView) findViewById(R.id.text);
|
||||
|
||||
final Runnable seq = buildSequence(mLocales);
|
||||
|
||||
Button b = (Button) findViewById(R.id.go);
|
||||
b.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View ignore) {
|
||||
mText.post(seq);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Runnable buildSequence(final Locale[] locales) {
|
||||
Runnable head = new Runnable() { public void run() { mergeBitmaps(locales); } };
|
||||
Locale prev = null;
|
||||
for (Locale loc : locales) {
|
||||
head = new TextCapture(loc, prev, head);
|
||||
prev = loc;
|
||||
}
|
||||
final Runnable fhead = head;
|
||||
final Locale floc = prev;
|
||||
return new Runnable() { public void run() { startSequence(fhead, floc); } };
|
||||
}
|
||||
|
||||
private void startSequence(Runnable firstRun, Locale firstLocale) {
|
||||
savedBitmaps.clear();
|
||||
switchTo(firstLocale);
|
||||
mText.postDelayed(firstRun, 200);
|
||||
}
|
||||
|
||||
private void saveBitmap(Bitmap b, String filename) {
|
||||
try {
|
||||
FileOutputStream fos = openFileOutput(filename, 0);
|
||||
b.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "failed to write PNG", e);
|
||||
}
|
||||
}
|
||||
|
||||
private int colorFor(byte b) {
|
||||
return 0xff000000 | (b<<16) | (b<<8) | b;
|
||||
}
|
||||
|
||||
private int colorFor(int b) {
|
||||
return 0xff000000 | (b<<16) | (b<<8) | b;
|
||||
}
|
||||
|
||||
private void mergeBitmaps(final Locale[] locales) {
|
||||
HashMap<String, Integer> countByLanguage = new HashMap<String, Integer>();
|
||||
|
||||
int height = 2;
|
||||
int width = 10;
|
||||
int maxHeight = 0;
|
||||
for (Locale loc : locales) {
|
||||
Bitmap b = savedBitmaps.get(loc);
|
||||
int h = b.getHeight();
|
||||
int w = b.getWidth();
|
||||
height += h+1;
|
||||
if (h > maxHeight) maxHeight = h;
|
||||
if (w > width) width = w;
|
||||
|
||||
String lang = loc.getLanguage();
|
||||
if (countByLanguage.containsKey(lang)) {
|
||||
countByLanguage.put(lang, countByLanguage.get(lang)+1);
|
||||
} else {
|
||||
countByLanguage.put(lang, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "output bitmap is " + width + " x " + height);
|
||||
Bitmap out = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
out.eraseColor(0xff000000);
|
||||
int[] pixels = new int[maxHeight * width];
|
||||
|
||||
int p = 0;
|
||||
for (Locale loc : locales) {
|
||||
Bitmap bm = savedBitmaps.get(loc);
|
||||
int h = bm.getHeight();
|
||||
int w = bm.getWidth();
|
||||
|
||||
String lang = loc.getLanguage();
|
||||
if (countByLanguage.get(lang) > 1) {
|
||||
lang = loc.toString();
|
||||
}
|
||||
Log.i(TAG, "encoding \"" + loc + "\" as \"" + lang + "\"");
|
||||
byte[] langBytes = lang.getBytes();
|
||||
out.setPixel(0, p, colorFor(w & 0xff));
|
||||
out.setPixel(1, p, colorFor(w >>> 8));
|
||||
out.setPixel(2, p, colorFor(h & 0xff));
|
||||
out.setPixel(3, p, colorFor(h >>> 8));
|
||||
out.setPixel(4, p, colorFor(langBytes.length));
|
||||
int x = 5;
|
||||
for (byte b : langBytes) {
|
||||
out.setPixel(x, p, colorFor(b));
|
||||
x++;
|
||||
}
|
||||
out.setPixel(x, p, colorFor(0));
|
||||
|
||||
p++;
|
||||
|
||||
bm.getPixels(pixels, 0, w, 0, 0, w, h);
|
||||
out.setPixels(pixels, 0, w, 0, p, w, h);
|
||||
p += h;
|
||||
}
|
||||
|
||||
// if no languages match, suppress text display by using a
|
||||
// single black pixel as the image.
|
||||
out.setPixel(0, p, colorFor(1));
|
||||
out.setPixel(1, p, colorFor(0));
|
||||
out.setPixel(2, p, colorFor(1));
|
||||
out.setPixel(3, p, colorFor(0));
|
||||
out.setPixel(4, p, colorFor(0));
|
||||
p++;
|
||||
|
||||
saveBitmap(out, "text-out.png");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user