am 0999f50b: am adf05af0: Merge "add an app to generate localized text bitmaps needed for recovery" into jb-mr1-dev
* commit '0999f50b7208c2dcdb57bac95148d83651a1cacb': add an app to generate localized text bitmaps needed for recovery
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