/** * DiskUsage - displays sdcard usage on android. * Copyright (C) 2008 Ivan Volosyuk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.google.android.diskusage.entity; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.Log; import com.google.android.diskusage.Cursor; import com.google.android.diskusage.R; import com.google.android.diskusage.opengl.DrawingCache; import com.google.android.diskusage.opengl.RenderingThread; public class FileSystemEntry { private static final Paint bg = new Paint(); private static final Paint bg_emptySpace = new Paint(); private static final Paint cursor_fg = new Paint(); private static final Paint fg_rect = new Paint(); // private static final Paint fg_rect = new Paint(); public static final Paint fg2 = new Paint(); private static final Paint fill_bg = new Paint(); private static final Paint textPaintFolder = new Paint(); private static final Paint textPaintFile = new Paint(); public static float ascent; public static float descent; private static String n_bytes; private static String n_kilobytes; private static String n_megabytes; private static String n_megabytes10; private static String n_megabytes100; private static String n_gigabytes; private static String n_gigabytes10; private static String n_gigabytes100; private static String dir_name_size_num_dirs; private static String dir_empty; private static String dir_name_size; public static FileSystemEntry deletedEntry; public boolean hasChildren() { return children != null && children.length != 0; } /** * Font size. Also accessed from FileSystemView. */ public static float fontSize; /** * Width of one element. Setup from FileSystemView when geometry changes. */ public static int elementWidth; static { bg.setColor(Color.parseColor("#060118")); bg_emptySpace.setColor(Color.parseColor("#063A43")); bg.setStyle(Paint.Style.FILL); // bg.setAlpha(255); fg_rect.setColor(Color.WHITE); fg_rect.setStyle(Paint.Style.STROKE); fg_rect.setFlags(fg_rect.getFlags() | Paint.ANTI_ALIAS_FLAG); // fg_rect.setColor(Color.WHITE); // fg_rect.setStyle(Paint.Style.STROKE); fg2.setColor(Color.parseColor("#18C5E7")); fg2.setStyle(Paint.Style.STROKE); fg2.setFlags(fg2.getFlags() | Paint.ANTI_ALIAS_FLAG); fill_bg.setColor(Color.WHITE); fill_bg.setStyle(Paint.Style.FILL); cursor_fg.setColor(Color.YELLOW); cursor_fg.setStyle(Paint.Style.STROKE); textPaintFolder.setColor(Color.WHITE); textPaintFolder.setStyle(Paint.Style.FILL_AND_STROKE); textPaintFolder.setFlags(textPaintFolder.getFlags() | Paint.ANTI_ALIAS_FLAG); textPaintFile.setColor(Color.parseColor("#18C5E7")); textPaintFile.setStyle(Paint.Style.FILL_AND_STROKE); textPaintFile.setFlags(textPaintFile.getFlags() | Paint.ANTI_ALIAS_FLAG); } // Object Fields: // The size suitable for painting without any operations (and sorting) // Bit layout: // 40 bits | 24 bits // sizeInBlocks | reminder // reminder is encoded file size information suitable for formating sizeString. // reminder: // 3 bits | 21 bits (2**18 = 44,040,192) // sizeMultiplier | size in multiplier of bytes // sizeMultiplier: // 000 = multiplier=1, format=(n_bytes "%d bytes", size) // 001 = multiplier=1024, format=(n_kilobytes "%d KiB", size) // 010 = multiplier=1024, format=(n_megabytes "%5.2f MiB", size / 1024.f) // 011 = multiplier=1024, format=(n_megabytes10 "%5.1f MiB", size / 1024.f) // 100 = multiplier=1024*1024, format=(n_megabytes100 "%d MiB", size) // 101 = multiplier=1024*1024, format=(n_gigabytes "%5.2f GiB", size/ 1024.f) // 110 = multiplier=1024*1024, format=(n_gigabytes10 "%5.1f GiB", size/ 1024.f) // 111 = multiplier=1024*1024*1024, format=(n_gigabytes100 "%d GiB", size) // Ranges for sizeMultipliers: // 0: sz < 1024: "%4.0f bytes", sz // 1: sz < 1024 * 1024: "%4.0f KiB", sz * (1f / 1024) // 2: sz < 1024 * 1024 * 10: "%5.2f MiB", sz * (1f / 1024 / 1024) // 3: sz < 1024 * 1024 * 200: "%5.1f MiB", sz * (1f / 1024 / 1024) // 4: sz >= 1024 * 1024 * 200: "%4.0f MiB", sz * (1f / 1024 / 1024) // // FIXME: remove outdate info: // reminder can be 0..blockSize (inclusive) // size in bytes = (size in blocks * blockSize) + reminder - blockSize; // FIXME: make private and update code which uses it public long encodedSize; public FileSystemEntry parent; public FileSystemEntry[] children; public String name; // public String sizeString; public DrawingCache drawingCache; private static final int MULTIPLIER_SHIFT=18; private static final int MULTIPLIER_MASK = 7 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_BYTES = 0 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_KBYTES = 1 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_MBYTES = 2 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_MBYTES10 = 3 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_MBYTES100 = 4 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_GBYTES = 5 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_GBYTES10 = 6 << MULTIPLIER_SHIFT; private static final int MULTIPLIER_GBYTES100 = 7 << MULTIPLIER_SHIFT; private static final int SIZE_MASK = (1 << MULTIPLIER_SHIFT) - 1; // static int blockSize; // will take for a while to make this break // 16Mb block size on mobile device... probably in year 2020. // probably 32 bits for maximum number of block will break before ~2016 public static final int blockOffset = 24; static final long blockMask = (1l << blockOffset) - 1; public long getSizeInBlocks() { return encodedSize >> blockOffset; } public long getSizeForRendering() { return encodedSize & ~blockMask; } public static String calcSizeStringFromEncoded(long encodedSize) { int size = SIZE_MASK & (int)encodedSize; switch (MULTIPLIER_MASK & (int)encodedSize) { case MULTIPLIER_BYTES: return String.format(n_bytes, size); case MULTIPLIER_KBYTES: return String.format(n_kilobytes, size); case MULTIPLIER_MBYTES: return String.format(n_megabytes, size * (1f / 1024)); case MULTIPLIER_MBYTES10: return String.format(n_megabytes10, size * (1f / 1024)); case MULTIPLIER_MBYTES100: return String.format(n_megabytes100, size); case MULTIPLIER_GBYTES: return String.format(n_gigabytes, size * (1f / 1024)); case MULTIPLIER_GBYTES10: return String.format(n_gigabytes10, size * (1f / 1024)); case MULTIPLIER_GBYTES100: return String.format(n_gigabytes100, size); } return ""; } public void clearDrawingCache() { if (drawingCache != null) { drawingCache.resetSizeString(); } } private long makeBytesPart(long size) { if (size < 1024) return size; if (size < 1024 * 1024) return MULTIPLIER_KBYTES | (size >> 10); if (size < 1024 * 1024 * 10 ) return MULTIPLIER_MBYTES | (size >> 10); if (size < 1024 * 1024 * 200) return MULTIPLIER_MBYTES10 | (size >> 10); if (size < (long) 1024 * 1024 * 1024) return MULTIPLIER_MBYTES100 | (size >> 20); if (size < (long) 1024 * 1024 * 1024 * 10) return MULTIPLIER_GBYTES | (size >> 20); if (size < (long) 1024 * 1024 * 1024 * 200) return MULTIPLIER_GBYTES10 | (size >> 20); return MULTIPLIER_GBYTES100 | (size >> 30); } public void setSizeInBlocks(long blocks, int blockSize) { long bytes = blocks * blockSize; encodedSize = (blocks << blockOffset) | makeBytesPart(bytes); } public FileSystemEntry initSizeInBytes(long bytes, int blockSize) { long blocks = (bytes + blockSize - 1) / blockSize; encodedSize = (blocks << blockOffset) | makeBytesPart(bytes); return this; } public FileSystemEntry initSizeInBytesAndBlocks(long bytes, long blocks, int blockSize) { encodedSize = (blocks << blockOffset) | makeBytesPart(bytes); return this; } public FileSystemEntry setChildren(FileSystemEntry[] children, int blockSize) { this.children = children; long blocks = 0; if (children == null) return this; for (int i = 0; i < children.length; i++) { blocks += children[i].getSizeInBlocks(); children[i].parent = this; } setSizeInBlocks(blocks, blockSize); return this; } public static class ExcludeFilter { public final Map<String, ExcludeFilter> childFilter; private static void addEntry( TreeMap<String, ArrayList<String>> filter, String name, String value) { ArrayList<String> entry = filter.get(name); if (entry == null) { entry = new ArrayList<String>(); filter.put(name, entry); } entry.add(value); } public ExcludeFilter(ArrayList<String> exclude_paths) { if (exclude_paths == null) { this.childFilter = null; return; } TreeMap<String, ArrayList<String>> filter = new TreeMap<String, ArrayList<String>>(); for(String path : exclude_paths) { String[] parts = path.split("/", 2); if (parts.length < 2) { addEntry(filter, path, null); } else { addEntry(filter, parts[0], parts[1]); } } TreeMap<String, ExcludeFilter> excludeFilter = new TreeMap<String, ExcludeFilter>(); for (Entry<String, ArrayList<String>> entry : filter.entrySet()) { boolean has_null = false; for (String part : entry.getValue()) { if (part == null) { has_null = true; break; } } if (has_null) { excludeFilter.put(entry.getKey(), new ExcludeFilter(null)); } else { excludeFilter.put(entry.getKey(), new ExcludeFilter(entry.getValue())); } } this.childFilter = excludeFilter; } } protected FileSystemEntry(FileSystemEntry parent, String name) { this.name = name; this.parent = parent; } public static FileSystemEntry makeNode( FileSystemEntry parent, String name) { return new FileSystemEntry(parent, name); } public static class Compare implements Comparator<FileSystemEntry> { @Override public final int compare(FileSystemEntry aa, FileSystemEntry bb) { if (aa.encodedSize == bb.encodedSize) { return 0; } return aa.encodedSize < bb.encodedSize ? 1 : -1; } } /** * For sorting according to size. */ public static Compare COMPARE = new Compare(); public FileSystemEntry create() { return new FileSystemEntry(null, this.name); } public class SearchInterruptedException extends RuntimeException { private static final long serialVersionUID = -3986013022885904101L; }; public FileSystemEntry copy() { if (Thread.interrupted()) throw new SearchInterruptedException(); FileSystemEntry copy = create(); if (this.children != null) { FileSystemEntry[] children = new FileSystemEntry[this.children.length]; for (int i = 0; i < this.children.length; i++) { FileSystemEntry childCopy = children[i] = this.children[i].copy(); childCopy.parent = copy; } copy.children = children; } copy.encodedSize = this.encodedSize; return copy; } public FileSystemEntry filterChildren(CharSequence pattern, int blockSize) { // res = Pattern.compile(Pattern.quote(pattern.toString()), Pattern.CASE_INSENSITIVE).matcher(name).find(); if (children == null) return null; ArrayList<FileSystemEntry> filtered_children = new ArrayList<FileSystemEntry>(); for (FileSystemEntry child : this.children) { FileSystemEntry childCopy = child.filter(pattern, blockSize); if (childCopy != null) { filtered_children.add(childCopy); } } if (filtered_children.size() == 0) return null; FileSystemEntry[] children = new FileSystemEntry[filtered_children.size()]; filtered_children.toArray(children); Arrays.sort(children, COMPARE); FileSystemEntry copy = create(); copy.children = children; long size = 0; for (FileSystemEntry child : children) { size += child.getSizeInBlocks(); child.parent = copy; } copy.setSizeInBlocks(size, blockSize); return copy; } public FileSystemEntry filter(CharSequence pattern, int blockSize) { if (name.toLowerCase().contains(pattern)) { return copy(); } return filterChildren(pattern, blockSize); } /** * Find index of directChild in 'children' field of this entry. * @param directChild * @return index of the directChild in 'children' field. */ public final int getIndexOf(FileSystemEntry directChild) { FileSystemEntry[] children0 = children; int len = children0.length; int i; for (i = 0; i < len; i++) { if (children0[i] == directChild) return i; } throw new RuntimeException("something broken"); } /** * Find entry which follows this entry in its the parent. * @return next entry in the same parent or this entry if there is no more entries */ public final FileSystemEntry getNext() { int index = parent.getIndexOf(this); if (index + 1 == parent.children.length) return this; return parent.children[index + 1]; } /** * Find entry which precedes this entry in its the parent. * @return previous entry in the same parent or this entry if the entry is first */ public final FileSystemEntry getPrev() { int index = parent.getIndexOf(this); if (index == 0) return this; return parent.children[index - 1]; } private DrawingCache getDrawingCache() { if (drawingCache != null) return drawingCache; DrawingCache drawingCache = new DrawingCache(this); this.drawingCache = drawingCache; return drawingCache; } // Copy pasted from paint() and changed to lower overhead on generic drawing code private static void paintSpecialGPU(long parent_size, FileSystemEntry[] entries, RenderingThread rt, float xoffset, float yoffset, float yscale, long clipLeft, long clipRight, long clipTop, long clipBottom, int screenHeight, int numSpecial) { // Deep one level in hierarchy: entries = entries[0].children; xoffset += elementWidth; clipLeft -= elementWidth; clipRight -= elementWidth; // Paint as paint(): FileSystemEntry children[] = entries; int len = children.length; long child_clipTop = clipTop; long child_clipBottom = clipBottom; float child_xoffset = xoffset + elementWidth; // Fast skip ordinary entries, FIXME: make root node special node with extra // field to get rid of this for (int i = 0; i < len - numSpecial; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; if (child_clipBottom < 0) { return; } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } for (int i = len - numSpecial; i < len; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; ///Log.d("DiskUsage", "child: child_clip_y0 = " + child_clip_y0); ///Log.d("DiskUsage", "child: child_clip_y1 = " + child_clip_y1); if (child_clipTop > csize) { child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; continue; } if (child_clipBottom < 0) { ///Log.d("DiskUsage", "skipped rest starting from " + c.name); return; } if (clipLeft < elementWidth) { // FIXME float windowHeight0 = screenHeight; float fontSize0 = fontSize; float top0 = top; float bottom0 = bottom; // FIXME: bg_emptySpace rt.specialSquare.draw(xoffset, top0, child_xoffset, bottom0); if (bottom - top > fontSize0 * 2) { float pos = (top + bottom) * 0.5f; if (pos < fontSize0) { if (bottom > 2 * fontSize0) { pos = fontSize0; } else { pos = bottom - fontSize0; } } else if (pos > windowHeight0 - fontSize0) { if (top < windowHeight0 - 2 * fontSize0) { pos = windowHeight0 - fontSize0; } else { pos = top + fontSize0; } } float pos1 = pos - descent; float pos2 = pos - ascent; DrawingCache cache = c.getDrawingCache(); // String sizeString = cache.getSizeString(); // int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); // String clippedName = c.name.substring(0, cliplen); // canvas.drawText(clippedName, xoffset + 2, pos1, fg); // canvas.drawText(sizeString, xoffset + 2, pos2, fg); cache.drawText(rt, xoffset + 2, pos1, elementWidth - 5); cache.drawSize(rt, xoffset + 2, pos2, elementWidth - 5); } else if (bottom - top > fontSize0) { // int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); // String clippedName = c.name.substring(0, cliplen); // canvas.drawText(clippedName, xoffset + 2, (top + bottom - ascent - descent) / 2, c.children == null ? fg2 : fg); DrawingCache cache = c.getDrawingCache(); cache.drawText(rt, xoffset + 2, (top + bottom - ascent - descent) / 2, elementWidth - 5); } } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } } // Copy pasted from paint() and changed to lower overhead on generic drawing code private static void paintSpecial(long parent_size, FileSystemEntry[] entries, Canvas canvas, float xoffset, float yoffset, float yscale, long clipLeft, long clipRight, long clipTop, long clipBottom, int screenHeight, int numSpecial) { // Deep one level in hierarchy: entries = entries[0].children; xoffset += elementWidth; clipLeft -= elementWidth; clipRight -= elementWidth; // Paint as paint(): FileSystemEntry children[] = entries; int len = children.length; long child_clipTop = clipTop; long child_clipBottom = clipBottom; float child_xoffset = xoffset + elementWidth; // Fast skip ordinary entries, FIXME: make root node special node with extra // field to get rid of this for (int i = 0; i < len - numSpecial; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; if (child_clipBottom < 0) { return; } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } for (int i = len - numSpecial; i < len; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; ///Log.d("DiskUsage", "child: child_clip_y0 = " + child_clip_y0); ///Log.d("DiskUsage", "child: child_clip_y1 = " + child_clip_y1); if (child_clipTop > csize) { child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; continue; } if (child_clipBottom < 0) { ///Log.d("DiskUsage", "skipped rest starting from " + c.name); return; } if (clipLeft < elementWidth) { // FIXME float windowHeight0 = screenHeight; float fontSize0 = fontSize; float top0 = top; float bottom0 = bottom; canvas.drawRect(xoffset, top0, child_xoffset, bottom0, bg_emptySpace); canvas.drawRect(xoffset, top0, child_xoffset, bottom0, fg_rect); if (bottom - top > fontSize0 * 2) { float pos = (top + bottom) * 0.5f; if (pos < fontSize0) { if (bottom > 2 * fontSize0) { pos = fontSize0; } else { pos = bottom - fontSize0; } } else if (pos > windowHeight0 - fontSize0) { if (top < windowHeight0 - 2 * fontSize0) { pos = windowHeight0 - fontSize0; } else { pos = top + fontSize0; } } float pos1 = pos - descent; float pos2 = pos - ascent; DrawingCache cache = c.getDrawingCache(); String sizeString = cache.getSizeString(); int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); String clippedName = c.name.substring(0, cliplen); canvas.drawText(clippedName, xoffset + 2, pos1, textPaintFolder); canvas.drawText(sizeString, xoffset + 2, pos2, textPaintFolder); } else if (bottom - top > fontSize0) { int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); String clippedName = c.name.substring(0, cliplen); canvas.drawText(clippedName, xoffset + 2, (top + bottom - ascent - descent) / 2, c.children == null ? textPaintFile : textPaintFolder); } } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } } private static void paintGPU(long parent_size, FileSystemEntry[] entries, RenderingThread rt, float xoffset, float yoffset, float yscale, long clipLeft, long clipRight, long clipTop, long clipBottom, int screenHeight) { FileSystemEntry children[] = entries; int len = children.length; long child_clipLeft = clipLeft - elementWidth; long child_clipRight = clipRight - elementWidth; long child_clipTop = clipTop; long child_clipBottom = clipBottom; float child_xoffset = xoffset + elementWidth; for (int i = 0; i < len; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; ///Log.d("DiskUsage", "child: child_clip_y0 = " + child_clip_y0); ///Log.d("DiskUsage", "child: child_clip_y1 = " + child_clip_y1); if (child_clipTop > csize) { child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; continue; } if (child_clipBottom < 0) { ///Log.d("DiskUsage", "skipped rest starting from " + c.name); return; } FileSystemEntry[] cchildren = c.children; if (cchildren != null) FileSystemEntry.paintGPU(c.getSizeForRendering(), cchildren, rt, child_xoffset, yoffset, yscale, child_clipLeft, child_clipRight, child_clipTop, child_clipBottom, screenHeight); if (bottom - top < 4 && deletedEntry != c) { bottom += parent_size * yscale; rt.smallSquare.draw(xoffset, top, child_xoffset, bottom); // canvas.drawRect(xoffset, top, child_xoffset, bottom, fg_rect); return; } if (clipLeft < elementWidth) { // FIXME float windowHeight0 = screenHeight; float fontSize0 = fontSize; float top0 = top; float bottom0 = bottom; boolean isFile = c.children == null; RenderingThread.Square square = isFile? rt.fileSquare : rt.dirSquare; square.draw(xoffset, top0, child_xoffset, bottom0); if (bottom - top > fontSize0 * 2) { float pos = (top + bottom) * 0.5f; if (pos < fontSize0) { if (bottom > 2 * fontSize0) { pos = fontSize0; } else { pos = bottom - fontSize0; } } else if (pos > windowHeight0 - fontSize0) { if (top < windowHeight0 - 2 * fontSize0) { pos = windowHeight0 - fontSize0; } else { pos = top + fontSize0; } } float pos1 = pos - descent; float pos2 = pos - ascent; DrawingCache cache = c.getDrawingCache(); // FIXME: text // FIXME: dir or file painted the same way cache.drawText(rt, xoffset + 2, pos1, elementWidth - 5); cache.drawSize(rt, xoffset + 2, pos2, elementWidth - 5); } else if (bottom - top > fontSize0) { DrawingCache cache = c.getDrawingCache(); // FIXME: dir and file painted the same way cache.drawText(rt, xoffset + 2, (top + bottom - ascent - descent) / 2, elementWidth - 5); } } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } } private static void paint(long parent_size, FileSystemEntry[] entries, Canvas canvas, float xoffset, float yoffset, float yscale, long clipLeft, long clipRight, long clipTop, long clipBottom, int screenHeight) { FileSystemEntry children[] = entries; int len = children.length; long child_clipLeft = clipLeft - elementWidth; long child_clipRight = clipRight - elementWidth; long child_clipTop = clipTop; long child_clipBottom = clipBottom; float child_xoffset = xoffset + elementWidth; for (int i = 0; i < len; i++) { FileSystemEntry c = children[i]; long csize = c.getSizeForRendering(); parent_size -= csize; float top = yoffset; float bottom = top + csize * yscale; ///Log.d("DiskUsage", "child: child_clip_y0 = " + child_clip_y0); ///Log.d("DiskUsage", "child: child_clip_y1 = " + child_clip_y1); if (child_clipTop > csize) { child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; continue; } if (child_clipBottom < 0) { ///Log.d("DiskUsage", "skipped rest starting from " + c.name); return; } FileSystemEntry[] cchildren = c.children; if (cchildren != null) FileSystemEntry.paint(c.getSizeForRendering(), cchildren, canvas, child_xoffset, yoffset, yscale, child_clipLeft, child_clipRight, child_clipTop, child_clipBottom, screenHeight); if (bottom - top < 4 && deletedEntry != c) { bottom += parent_size * yscale; canvas.drawRect(xoffset, top, child_xoffset, bottom, fill_bg); canvas.drawRect(xoffset, top, child_xoffset, bottom, fg_rect); return; } if (clipLeft < elementWidth) { // FIXME float windowHeight0 = screenHeight; float fontSize0 = fontSize; float top0 = top; float bottom0 = bottom; // float clipedtop0 = top0 < 0 ? 0 : top0; // float clipedbottom0 = bottom0 > 800 ? 800 : bottom0; canvas.drawRect(xoffset, top0, child_xoffset, bottom0, bg); canvas.drawRect(xoffset, top0, child_xoffset, bottom0, fg_rect); if (bottom - top > fontSize0 * 2) { float pos = (top + bottom) * 0.5f; if (pos < fontSize0) { if (bottom > 2 * fontSize0) { pos = fontSize0; } else { pos = bottom - fontSize0; } } else if (pos > windowHeight0 - fontSize0) { if (top < windowHeight0 - 2 * fontSize0) { pos = windowHeight0 - fontSize0; } else { pos = top + fontSize0; } } float pos1 = pos - descent; float pos2 = pos - ascent; DrawingCache cache = c.getDrawingCache(); String sizeString = cache.getSizeString(); int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); String clippedName = c.name.substring(0, cliplen); Paint paint = c.children == null ? textPaintFile : textPaintFolder; canvas.drawText(clippedName, xoffset + 2, pos1, paint); canvas.drawText(sizeString, xoffset + 2, pos2, paint); } else if (bottom - top > fontSize0) { int cliplen = fg2.breakText(c.name, true, elementWidth - 4, null); String clippedName = c.name.substring(0, cliplen); Paint paint = c.children == null ? textPaintFile : textPaintFolder; canvas.drawText(clippedName, xoffset + 2, (top + bottom - ascent - descent) / 2, paint); } } child_clipTop -= csize; child_clipBottom -= csize; yoffset = bottom; } } public final void paintGPU(RenderingThread rt, Rect bounds, Cursor cursor, long viewTop, float viewDepth, float yscale, int screenHeight, int numSpecialEntries) { // scale conversion: // window_y = yscale * world_y // world_y = window_y / yscale // offset conversion: // window_y = yscale * (world_y - rootOffset) // world_y = window_y / yscale + rootOffset //viewTop = 23 * 1024 * 1024; //viewDepth = 0.3f; int viewLeft = (int)(viewDepth * elementWidth); // screen clip area to world conversion: long clipTop = (long)(bounds.top / yscale) + viewTop; long clipBottom = (long)(bounds.bottom / yscale) + viewTop; int clipLeft = bounds.left + viewLeft; int clipRight = bounds.right + viewLeft; float xoffset = -viewLeft; float yoffset = -viewTop * yscale; // X coords: // xoffset - screen position of current object on the screen // clip_x0, clip_x1 - clip area in coords of current object // screen_clip_x0 = xoffset + clip_x0 // screen_clip_x1 = xoffset + clip_x1 // Y coords: // yoffset - screen position of current object on the screen // clip_y0, clip_y1 - clip area in world coords relative to current object // screen_clip_y0 = yscale * (clip_y0 - elementOffset) // screen_clip_y1 = yscale * (clip_y1 - elementOffset) paintGPU(getSizeForRendering(), children, rt, xoffset, yoffset, yscale, clipLeft, clipRight, clipTop, clipBottom, screenHeight); paintSpecialGPU(getSizeForRendering(), children, rt, xoffset, yoffset, yscale, clipLeft, clipRight, clipTop, clipBottom, screenHeight, numSpecialEntries); // paint position float cursorLeft = cursor.depth * elementWidth + xoffset; float cursorTop = (cursor.top - viewTop) * yscale; float cursorRight = cursorLeft + elementWidth; float cursorBottom = cursorTop + cursor.position.getSizeForRendering() * yscale; rt.cursorSquare.drawFrame(cursorLeft, cursorTop, cursorRight, cursorBottom); } public final void paint(Canvas canvas, Rect bounds, Cursor cursor, long viewTop, float viewDepth, float yscale, int screenHeight, int numSpecialEntries) { // scale conversion: // window_y = yscale * world_y // world_y = window_y / yscale // offset conversion: // window_y = yscale * (world_y - rootOffset) // world_y = window_y / yscale + rootOffset //viewTop = 23 * 1024 * 1024; //viewDepth = 0.3f; int viewLeft = (int)(viewDepth * elementWidth); // screen clip area to world conversion: long clipTop = (long)(bounds.top / yscale) + viewTop; long clipBottom = (long)(bounds.bottom / yscale) + viewTop; int clipLeft = bounds.left + viewLeft; int clipRight = bounds.right + viewLeft; float xoffset = -viewLeft; float yoffset = -viewTop * yscale; // X coords: // xoffset - screen position of current object on the screen // clip_x0, clip_x1 - clip area in coords of current object // screen_clip_x0 = xoffset + clip_x0 // screen_clip_x1 = xoffset + clip_x1 // Y coords: // yoffset - screen position of current object on the screen // clip_y0, clip_y1 - clip area in world coords relative to current object // screen_clip_y0 = yscale * (clip_y0 - elementOffset) // screen_clip_y1 = yscale * (clip_y1 - elementOffset) paint(getSizeForRendering(), children, canvas, xoffset, yoffset, yscale, clipLeft, clipRight, clipTop, clipBottom, screenHeight); paintSpecial(getSizeForRendering(), children, canvas, xoffset, yoffset, yscale, clipLeft, clipRight, clipTop, clipBottom, screenHeight, numSpecialEntries); // paint position float cursorLeft = cursor.depth * elementWidth + xoffset; float cursorTop = (cursor.top - viewTop) * yscale; float cursorRight = cursorLeft + elementWidth; float cursorBottom = cursorTop + cursor.position.getSizeForRendering() * yscale; canvas.drawRect(cursorLeft, cursorTop, cursorRight, cursorBottom, cursor_fg); } /** * Calculate size string for specified file length in bytes. * * Currently used by delete activity preview file list loader. * * @param sz file size in bytes * @return formated size string */ public static String calcSizeString(float sz) { if (sz < 1024 * 1024 * 10) { if (sz < 1024 * 1024) { if (sz < 1024) { if (sz < 0) sz = 0; return String.format(n_bytes, (int)sz); } return String.format(n_kilobytes, (int)(sz * (1f / 1024))); } return String.format(n_megabytes, sz * (1f / 1024 / 1024)); } if (sz < 1024 * 1024 * 200) { return String.format(n_megabytes10, sz * (1f / 1024 / 1024)); } return String.format(n_megabytes100, (int)(sz * (1f / 1024 / 1024))); } public final String sizeString() { return calcSizeStringFromEncoded(encodedSize); } // public final String pathFromRoot(FileSystemEntry root) { // String res = toTitleString(); // if (parent == root) return res; // else return parent.relativePath(root) + "/" + res; // } public final String toTitleString() { String sizeString0 = sizeString(); if (children != null && children.length != 0) return String.format(dir_name_size_num_dirs, name, sizeString0, children.length); else if (getSizeInBlocks() == 0) { return String.format(dir_empty, name); } else { return String.format(dir_name_size, name, sizeString0); } } public final String path2() { ArrayList<String> pathElements = new ArrayList<String>(); FileSystemEntry current = this; while (current != null) { pathElements.add(current.name); current = current.parent; } pathElements.remove(pathElements.size() - 1); pathElements.remove(pathElements.size() - 1); StringBuilder path = new StringBuilder(); String sep = ""; for (int i = pathElements.size() - 1; i >= 0; i--) { path.append(sep); path.append(pathElements.get(i)); sep = "/"; } return path.toString(); } public final String absolutePath() { if (this == null) { // FIXME: to prevent crash appeared on some users. return ""; } if (this instanceof FileSystemRoot) { return ((FileSystemRoot)this).rootPath; } return parent.absolutePath() + "/" + name; } /** * Find depth of 'entry' in current element. * @param entry * @return 1 for depth equal 1 and so on */ public final int depth(FileSystemEntry entry) { int d = 0; FileSystemEntry root = this; while(entry != root) { entry = entry.parent; d++; } return d; } /** * Find and return entry on specified depth and offset in this entry used as root. * @param maxDepth * @param offset * @return nearest entry to the specified conditions */ public final FileSystemEntry findEntry(int maxDepth, long offset) { long currOffset = 0; FileSystemEntry entry = this; FileSystemEntry[] children0 = children; // Log.d("DiskUsage", "Starting entry search at " + entry.name); for (int depth = 0; depth < maxDepth; depth++) { int nchildren = children0.length; // Log.d("DiskUsage", " Entry = " + entry.name); for (int c = 0; c < nchildren; c++) { FileSystemEntry e = children0[c]; long size = e.getSizeForRendering(); if (currOffset + size < offset) { currOffset += size; continue; } // found entry entry = e; children0 = e.children; if (children0 == null) return entry; break; } } return entry; } /** * Returns offset in bytes (world coordinates) from start of this * object to the start of 'cursor' object. * @param cursor * @return offset in bytes */ public final long getOffset(FileSystemEntry cursor) { long offset = 0; FileSystemEntry dir; FileSystemEntry root = this; // Log.d("diskusage", "getOffset()"); while (cursor != root) { // Log.d("diskusage", "cursor = " + (cursor != null) + " root = " + (root != null)); // Log.d("diskusage", "cursor = " + cursor.name + " root = " + root.name); dir = cursor.parent; FileSystemEntry[] children = dir.children; int len = children.length; for (int i = 0; i < len; i++) { FileSystemEntry e = children[i]; if (e == cursor) break; offset += e.getSizeForRendering(); } cursor = dir; } return offset; } // FIXME: no resort needed public final void remove(int blockSize) { FileSystemEntry[] children0 = parent.children; int len = children0.length; for (int i = 0; i < len; i++) { if (children0[i] != this) continue; // executed only once: parent.children = new FileSystemEntry[len - 1]; System.arraycopy(children0, 0, parent.children, 0, i); System.arraycopy(children0, i + 1, parent.children, i, len - i - 1); //java.util.Arrays.sort(parent.children, this); FileSystemEntry parent0 = parent; long blocks = getSizeInBlocks(); while (parent0 != null) { parent0.setSizeInBlocks(parent0.getSizeInBlocks() - blocks, blockSize); parent0.clearDrawingCache(); // java.util.Arrays.sort(parent0.children, this); parent0 = parent0.parent; } return; } // FIXME: the exception was thrown somehow // throw new RuntimeException("child is not found: " + this); } public final void insert(FileSystemEntry newEntry, int blockSize) { FileSystemEntry[] children0 = new FileSystemEntry[children.length + 1]; System.arraycopy(children, 0, children0, 0, children.length); children0[children.length] = newEntry; children = children0; newEntry.parent = this; FileSystemEntry parent0 = this; long blocks = newEntry.getSizeInBlocks(); while (parent0 != null) { java.util.Arrays.sort(children, COMPARE); parent0.setSizeInBlocks(parent0.getSizeInBlocks() + blocks, blockSize); parent0.clearDrawingCache(); parent0 = parent0.parent; } } /** * Walks through the path and finds the specified entry, null otherwise. * @param exactMatch TODO */ public FileSystemEntry getEntryByName(String path, boolean exactMatch) { Log.d("diskusage", "getEntryForName = " + path); String[] pathElements = path.split("/"); FileSystemEntry entry = this; outer: for (int i = 0; i < pathElements.length; i++) { String name = pathElements[i]; FileSystemEntry[] children = entry.children; if (children == null) { return null; } for (int j = 0; j < children.length; j++) { entry = children[j]; if (name.equals(entry.name)) { continue outer; } } return null; } return entry; } public static final int padding = 4; public static void setupStrings(Context context) { if (n_bytes != null) return; n_bytes = context.getString(R.string.n_bytes); n_kilobytes = context.getString(R.string.n_kilobytes); n_megabytes = context.getString(R.string.n_megabytes); n_megabytes10 = context.getString(R.string.n_megabytes10); n_megabytes100 = context.getString(R.string.n_megabytes100); n_gigabytes = context.getString(R.string.n_gigabytes); n_gigabytes10 = context.getString(R.string.n_gigabytes10); n_gigabytes100 = context.getString(R.string.n_gigabytes100); dir_name_size_num_dirs = context.getString(R.string.dir_name_size_num_dirs); dir_empty = context.getString(R.string.dir_empty); dir_name_size = context.getString(R.string.dir_name_size); } public static void updateFontsLegacy(Context context) { float textSize = context.getResources().getDisplayMetrics().scaledDensity * 12 + 0.5f; if (textSize < 10) textSize = 10; updateFonts(textSize); } public static void updateFonts(float textSize) { textPaintFile.setTextSize(textSize); textPaintFolder.setTextSize(textSize); ascent = textPaintFolder.ascent(); descent = textPaintFolder.descent(); fontSize = descent - ascent; } // public final void getAllChildren(List<String> out, FileSystemEntry deleteRoot) { // FileSystemEntry[] sortedChildren = new FileSystemEntry[children.length]; // System.arraycopy(children, 0, sortedChildren, 0, children.length); // Arrays.sort(sortedChildren, alphaComparator); // for (int i = 0; i < children.length; i++) { // FileSystemEntry child = children[i]; // if (child.children != null) child.getAllChildren(out, deleteRoot); // else out.add(child.pathFromRoot(deleteRoot)); // } // } private void validate0() { if (parent != null) { parent.getIndexOf(this); validateRecursive(); parent.validate0(); return; } validateRecursive(); } private void validateRecursive() { if (children == null) return; for (int i = 0; i < children.length; i++) { if (children[i].parent != this) throw new RuntimeException("corrupted: " + this.path2() + " <> " + children[i].name); children[i].validateRecursive(); } } /** * Just files, no directories. * @return count */ public int getNumFiles() { if (this instanceof FileSystemEntrySmall) { return ((FileSystemEntrySmall)this).numFiles; } if (children == null) return 1; int numFiles = 0; boolean hasFile = false; for (FileSystemEntry entry : children) { if (entry.children == null) hasFile = true; numFiles += entry.getNumFiles(); } if (hasFile) numFiles++; return numFiles; } }