| /* |
| * 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.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Locale; |
| |
| /** |
| * 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-* |
| * |
| * - 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"; |
| |
| 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; |
| case 4: mStringId = R.string.recovery_installing_security; break; |
| } |
| } |
| @Override public void onNothingSelected(AdapterView parent) { } |
| }); |
| |
| mText = (TextView) findViewById(R.id.text); |
| |
| String[] localeNames = getAssets().getLocales(); |
| Arrays.sort(localeNames, new Comparator<String>() { |
| // Override the string comparator so that en is sorted behind en_US. |
| // As a result, en_US will be matched first in recovery. |
| @Override |
| public int compare(String s1, String s2) { |
| if (s1.equals(s2)) { |
| return 0; |
| } else if (s1.startsWith(s2)) { |
| return -1; |
| } else if (s2.startsWith(s1)) { |
| return 1; |
| } |
| return s1.compareTo(s2); |
| } |
| }); |
| |
| ArrayList<Locale> locales = new ArrayList<Locale>(); |
| for (String localeName : localeNames) { |
| Log.i(TAG, "locale = " + localeName); |
| if (!localeName.isEmpty()) { |
| locales.add(Locale.forLanguageTag(localeName)); |
| } |
| } |
| |
| final Runnable seq = buildSequence(locales.toArray(new Locale[0])); |
| |
| 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(); |
| |
| bm.getPixels(pixels, 0, w, 0, 0, w, h); |
| |
| // Find the rightmost and leftmost columns with any |
| // nonblack pixels; we'll copy just that region to the |
| // output image. |
| |
| int right = w; |
| while (right > 1) { |
| boolean all_black = true; |
| for (int j = 0; j < h; ++j) { |
| if (pixels[j*w+right-1] != 0xff000000) { |
| all_black = false; |
| break; |
| } |
| } |
| if (all_black) { |
| --right; |
| } else { |
| break; |
| } |
| } |
| |
| int left = 0; |
| while (left < right-1) { |
| boolean all_black = true; |
| for (int j = 0; j < h; ++j) { |
| if (pixels[j*w+left] != 0xff000000) { |
| all_black = false; |
| break; |
| } |
| } |
| if (all_black) { |
| ++left; |
| } else { |
| break; |
| } |
| } |
| |
| // Make the last country variant for a given language be |
| // the catch-all for that language (because recovery will |
| // take the first one that matches). |
| String lang = loc.getLanguage(); |
| if (countByLanguage.get(lang) > 1) { |
| countByLanguage.put(lang, countByLanguage.get(lang)-1); |
| lang = loc.toString(); |
| } |
| int tw = right - left; |
| Log.i(TAG, "encoding \"" + loc + "\" as \"" + lang + "\": " + tw + " x " + h); |
| byte[] langBytes = lang.getBytes(); |
| out.setPixel(0, p, colorFor(tw & 0xff)); |
| out.setPixel(1, p, colorFor(tw >>> 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++; |
| |
| out.setPixels(pixels, left, w, 0, p, tw, 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"); |
| Log.i(TAG, "wrote text-out.png"); |
| } |
| } |