<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Gorges Blog &#187; Matt Clark</title>
	<atom:link href="http://blog.GORGES.us/author/mclark/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.GORGES.us</link>
	<description>Web Sites that Grow Your Business - our blog</description>
	<lastBuildDate>Mon, 19 Jul 2010 20:48:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Developing Apps within Android&#8217;s 16MB Memory Limit</title>
		<link>http://blog.GORGES.us/2010/07/developing-apps-within-androids-16mb-memory-limit/</link>
		<comments>http://blog.GORGES.us/2010/07/developing-apps-within-androids-16mb-memory-limit/#comments</comments>
		<pubDate>Fri, 16 Jul 2010 13:49:19 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Mobile Development]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Android DDMT]]></category>
		<category><![CDATA[device apps]]></category>
		<category><![CDATA[device programming]]></category>
		<category><![CDATA[Eclipse MAT]]></category>
		<category><![CDATA[memory]]></category>
		<category><![CDATA[mobile apps]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=269</guid>
		<description><![CDATA[One challenge to developing for the Android platform is how to squeeze everything into 16 megabytes of heap space.  This blog post lists several solutions for memory-limited Android apps.]]></description>
			<content:encoded><![CDATA[<p>One challenge to developing for the Android platform is how to  squeeze everything into 16 megabytes of heap space.  App-phones with  16GB and 32GB are common, but that is solid state storage and not RAM.   For Android applications, the limit for each application is 16MB (24MB  on newer Droid and Nexus One phones).</p>
<p>Images, audio, and video are  memory-intensive items, and many apps have these features. There are  tools to help monitor memory use (e.g. Eclipse MAT, Android DDMS), and  these tools are good for diagnosing problems, but you still need to  understand enough to be able to fix memory leaks.</p>
<p>Here are  some ideas for reducing the memory footprint of your application:</p>
<p><strong>Reduce  image sizes</strong>:  Lower the width, height, pixel bit-depth, and  compress your images as much as you can.  Of course when an image is  uncompressed and loaded into a Bitmap object then it takes up more  memory, but you can reduce the memory footprint by lowering the image  quality (for example see: View.setDrawingCacheQuality and  View.DRAWING_CACHE_QUALITY_LOW) or even disabling the cache on views containing images.</p>
<p><strong>Lower  audio and video bitrates:</strong> I don&#8217;t know this for a fact, but I  would guess that a lower-bitrate audio stream may have a smaller memory  requirement during playback.  For example a mono-48 kbit/second audio  file would require decoded fewer samples per second than a stereo-192  kbit/second file. (Please comment to this post if you test this theory  or know the answer.)</p>
<p><strong>Destroy or reuse objects:</strong> When  you can, re-use objects and make sure that old objects are  fully-destroyed. when they are no used anymore.  Even better, never  create objects in the first place if possible.  This is especially try  for bitmap objects &#8211; be sure to call the Bitmap.recycle() method.  Remember to clear callback methods of objects before destroying them,  since otherwise an object may not be properly returned to the memory  heap during a java garbage collection operation.</p>
<p><strong>Use final  and static</strong>:  Virtual methods take up more space (and are slower)  than static methods.  Final variables and arrays are stored in code  space and not the memory heap.  Granted the difference is very small  compared with 16MB of space, but every little bit counts!</p>
<p><strong>Separate  applications for localization</strong>:  If you are developing apps for  multiple languages, consider creating separate applications instead of  including all the language-specific strings, images, audio files, and  videos in a single application.</p>
<p><strong>Rely on external storage</strong>:   If you know that there is smart-card memory available, use that to store  data instead of in memory.</p>
<p><strong>Revert to an earlier Android SDK:</strong> When we reverted our most-memory-challenged Android application from  Android 2.2 to Android 1.5, we gained two important things: first, we  are now compatible with almost all existing Android phones; and second  we reduced our memory footprint by almost a megabyte.  This latter  statement is extremely interesting, since it indicates that the Android  framework is getting bloated by all the new features added between  versions 1.5 and 2.2.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2010/07/developing-apps-within-androids-16mb-memory-limit/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Distributed Dictionary Attack Solutions</title>
		<link>http://blog.GORGES.us/2010/06/distributed-dictionary-attack-solutions/</link>
		<comments>http://blog.GORGES.us/2010/06/distributed-dictionary-attack-solutions/#comments</comments>
		<pubDate>Sat, 26 Jun 2010 11:58:10 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[System Administration]]></category>
		<category><![CDATA[dictionary attack]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[zombie]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=283</guid>
		<description><![CDATA[Learn some ideas on how to thwart distributed dictionary attacks on your servers.]]></description>
			<content:encoded><![CDATA[<p>We have had the misfortune of having attempted distributed dictionary attacks on our Linux servers.  A dictionary attack uses a long list of common usernames and passwords trying to find a way to gain a foothold and eventually root access of a password-protected server.</p>
<p>Our servers use utilities such as <a href="http://www.fail2ban.org/">fail2ban</a> or <a href="http://denyhosts.sourceforge.net/">denyhosts</a> that look for repeated failed login attempts, and once found they direct the firewall service to ban the originating IP addresses.  However this technique fails when the attack is distributed among thousands of compromised &#8220;zombie&#8221; computers that are doing the bidding of a malicious hacker.</p>
<p>Our log files correctly diagnosed each attempt from an individual IP address, but a new attempt was immediately started from a different IP address.  We were clearly looking at an attack coordinated from a single unknown source.</p>
<p>There are several ways to reduce or thwart these attacks, including:</p>
<ul>
<li>never allow remote root logins (but attacks still occur on  non-root-user names)</li>
<li>have a chroot jail shell in case an attack on a non-root account succeeds</li>
<li>changing SSH service to use a non-obvious port (such as port 5022 instead of 22)</li>
<li>deactivate password authentication and rely exclusively on authentication keys</li>
<li>restrict allowed IP address by country of origin</li>
<li>only allow certain IP addresses or ranges of addresses to have access</li>
</ul>
<p>We chose to implement more than one of these solutions, and I wanted to share some techniques we used for our implementation.</p>
<p>For the last rule that only allows certain IP addresses, I wanted to start with a list of valid IP addresses used in the last month.  The following script extracts these IP numbers from our SSH log file, sorts them alphabetically, and then removes duplicates.  Note that this server uses Fedora &#8211; you may need to tweak it for other linux distributions.</p>
<pre class="brush: java;">
root# fgrep &quot;Accepted&quot; /var/log/secure* | awk '{print $11}' | sort | uniq
166.77.6.4
205.232.34.1
67.255.5.155
...
</pre>
<p>The IP addresses from the above script should be added to the file <strong>/etc/hosts.allow</strong> in the following format:</p>
<pre class="brush: plain;">
# hosts.allow   This file contains access rules which are used to
#               allow or deny connections to network services that
#               either use the tcp_wrappers library or that have been
#               started through a tcp_wrappers-enabled xinetd.
#
#               See 'man 5 hosts_options' and 'man 5 hosts_access'
#               for information on rule syntax.
#               See 'man tcpd' for information on tcp_wrappers

# allow local addresses
all: 127.0.0.1
all: 192.168.1.*

# valid IP addresses gathered June 2010
all: 166.77.6.4
all: 205.232.34.1
all: 67.255.5.155
...
</pre>
<p>Now disallow all other IP addresses for SSH by editing the file <strong>/etc/hosts.deny</strong>:</p>
<pre class="brush: plain;">
# hosts.deny    This file contains access rules which are used to
#               deny connections to network services that either use
#               the tcp_wrappers library or that have been
#               started through a tcp_wrappers-enabled xinetd.
#
#               The rules in this file can also be set up in
#               /etc/hosts.allow with a 'deny' option instead.
#
#               See 'man 5 hosts_options' and 'man 5 hosts_access'
#               for information on rule syntax.
#               See 'man tcpd' for information on tcp_wrappers
#
# The portmap line is redundant, but it is left to remind you that
# the new secure portmap uses hosts.deny and hosts.allow.  In particular
# you should know that NFS uses portmap!

# deny SSH service except for IP numbers in /etc/hosts.allow file
sshd: all
</pre>
<p>Restart your SSH service, and your server should now be a bit more secure against distributed dictionary attacks:</p>
<pre class="brush: plain;">
root# service sshd restart
</pre>
<p>An Internet search using keywords from the other mentioned solutions above will teach you how to change SSH port, disallow password authentication, etc.</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;"><!--[if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:TrackMoves /> <w:TrackFormatting /> <w:PunctuationKerning /> <w:ValidateAgainstSchemas /> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:DoNotPromoteQF /> <w:LidThemeOther>EN-US</w:LidThemeOther> <w:LidThemeAsian>X-NONE</w:LidThemeAsian> <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript> <w:Compatibility> <w:BreakWrappedTables /> <w:SnapToGridInCell /> <w:WrapTextWithPunct /> <w:UseAsianBreakRules /> <w:DontGrowAutofit /> <w:SplitPgBreakAndParaMark /> <w:DontVertAlignCellWithSp /> <w:DontBreakConstrainedForcedTables /> <w:DontVertAlignInTxbx /> <w:Word11KerningPairs /> <w:CachedColBalance /> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> <m:mathPr> <m:mathFont m:val="Cambria Math" /> <m:brkBin m:val="before" /> <m:brkBinSub m:val="&#45;-" /> <m:smallFrac m:val="off" /> <m:dispDef /> <m:lMargin m:val="0" /> <m:rMargin m:val="0" /> <m:defJc m:val="centerGroup" /> <m:wrapIndent m:val="1440" /> <m:intLim m:val="subSup" /> <m:naryLim m:val="undOvr" /> </m:mathPr></w:WordDocument> </xml><![endif]--><!--[if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"   DefSemiHidden="true" DefQFormat="false" DefPriority="99"   LatentStyleCount="267"> <w:LsdException Locked="false" Priority="0" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Normal" /> <w:LsdException Locked="false" Priority="9" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="heading 1" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9" /> <w:LsdException Locked="false" Priority="39" Name="toc 1" /> <w:LsdException Locked="false" Priority="39" Name="toc 2" /> <w:LsdException Locked="false" Priority="39" Name="toc 3" /> <w:LsdException Locked="false" Priority="39" Name="toc 4" /> <w:LsdException Locked="false" Priority="39" Name="toc 5" /> <w:LsdException Locked="false" Priority="39" Name="toc 6" /> <w:LsdException Locked="false" Priority="39" Name="toc 7" /> <w:LsdException Locked="false" Priority="39" Name="toc 8" /> <w:LsdException Locked="false" Priority="39" Name="toc 9" /> <w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption" /> <w:LsdException Locked="false" Priority="10" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Title" /> <w:LsdException Locked="false" Priority="1" Name="Default Paragraph Font" /> <w:LsdException Locked="false" Priority="11" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Subtitle" /> <w:LsdException Locked="false" Priority="22" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Strong" /> <w:LsdException Locked="false" Priority="20" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Emphasis" /> <w:LsdException Locked="false" Priority="59" SemiHidden="false"    UnhideWhenUsed="false" Name="Table Grid" /> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text" /> <w:LsdException Locked="false" Priority="1" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="No Spacing" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 1" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 1" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 1" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 1" /> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision" /> <w:LsdException Locked="false" Priority="34" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="List Paragraph" /> <w:LsdException Locked="false" Priority="29" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Quote" /> <w:LsdException Locked="false" Priority="30" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Intense Quote" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 1" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 1" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 1" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 1" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 1" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 2" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 2" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 2" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 2" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 2" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 2" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 2" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 2" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 2" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 3" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 3" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 3" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 3" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 3" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 3" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 3" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 3" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 3" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 4" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 4" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 4" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 4" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 4" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 4" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 4" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 4" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 4" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 5" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 5" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 5" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 5" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 5" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 5" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 5" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 5" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 5" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Shading Accent 6" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false"    UnhideWhenUsed="false" Name="Light List Accent 6" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false"    UnhideWhenUsed="false" Name="Light Grid Accent 6" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 1 Accent 6" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium List 2 Accent 6" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false"    UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false"    UnhideWhenUsed="false" Name="Dark List Accent 6" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Shading Accent 6" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful List Accent 6" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false"    UnhideWhenUsed="false" Name="Colorful Grid Accent 6" /> <w:LsdException Locked="false" Priority="19" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis" /> <w:LsdException Locked="false" Priority="21" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis" /> <w:LsdException Locked="false" Priority="31" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference" /> <w:LsdException Locked="false" Priority="32" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Intense Reference" /> <w:LsdException Locked="false" Priority="33" SemiHidden="false"    UnhideWhenUsed="false" QFormat="true" Name="Book Title" /> <w:LsdException Locked="false" Priority="37" Name="Bibliography" /> <w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading" /> </w:LatentStyles> </xml><![endif]--><!--  /* Font Definitions */  @font-face 	{font-family:"Cambria Math"; 	panose-1:2 4 5 3 5 4 6 3 2 4; 	mso-font-charset:0; 	mso-generic-font-family:roman; 	mso-font-pitch:variable; 	mso-font-signature:-1610611985 1107304683 0 0 415 0;} @font-face 	{font-family:Calibri; 	panose-1:2 15 5 2 2 2 4 3 2 4; 	mso-font-charset:0; 	mso-generic-font-family:swiss; 	mso-font-pitch:variable; 	mso-font-signature:-520092929 1073786111 9 0 415 0;}  /* Style Definitions */  p.MsoNormal, li.MsoNormal, div.MsoNormal 	{mso-style-unhide:no; 	mso-style-qformat:yes; 	mso-style-parent:""; 	margin:0in; 	margin-bottom:.0001pt; 	mso-pagination:widow-orphan; 	font-size:11.0pt; 	font-family:"Calibri","sans-serif"; 	mso-ascii-font-family:Calibri; 	mso-ascii-theme-font:minor-latin; 	mso-fareast-font-family:Calibri; 	mso-fareast-theme-font:minor-latin; 	mso-hansi-font-family:Calibri; 	mso-hansi-theme-font:minor-latin; 	mso-bidi-font-family:"Times New Roman"; 	mso-bidi-theme-font:minor-bidi;} span.EmailStyle15 	{mso-style-type:personal; 	mso-style-noshow:yes; 	mso-style-unhide:no; 	mso-ansi-font-size:11.0pt; 	mso-bidi-font-size:11.0pt; 	font-family:"Calibri","sans-serif"; 	mso-ascii-font-family:Calibri; 	mso-ascii-theme-font:minor-latin; 	mso-fareast-font-family:Calibri; 	mso-fareast-theme-font:minor-latin; 	mso-hansi-font-family:Calibri; 	mso-hansi-theme-font:minor-latin; 	mso-bidi-font-family:"Times New Roman"; 	mso-bidi-theme-font:minor-bidi; 	color:windowtext;} .MsoChpDefault 	{mso-style-type:export-only; 	mso-default-props:yes; 	mso-ascii-font-family:Calibri; 	mso-ascii-theme-font:minor-latin; 	mso-fareast-font-family:Calibri; 	mso-fareast-theme-font:minor-latin; 	mso-hansi-font-family:Calibri; 	mso-hansi-theme-font:minor-latin; 	mso-bidi-font-family:"Times New Roman"; 	mso-bidi-theme-font:minor-bidi;} @page WordSection1 	{size:8.5in 11.0in; 	margin:1.0in 1.0in 1.0in 1.0in; 	mso-header-margin:.5in; 	mso-footer-margin:.5in; 	mso-paper-source:0;} div.WordSection1 	{page:WordSection1;} --><!--[if gte mso 10]> <mce:style><!   /* Style Definitions */  table.MsoNormalTable 	{mso-style-name:"Table Normal"; 	mso-tstyle-rowband-size:0; 	mso-tstyle-colband-size:0; 	mso-style-noshow:yes; 	mso-style-priority:99; 	mso-style-qformat:yes; 	mso-style-parent:""; 	mso-padding-alt:0in 5.4pt 0in 5.4pt; 	mso-para-margin:0in; 	mso-para-margin-bottom:.0001pt; 	mso-pagination:widow-orphan; 	font-size:11.0pt; 	font-family:"Calibri","sans-serif"; 	mso-ascii-font-family:Calibri; 	mso-ascii-theme-font:minor-latin; 	mso-fareast-font-family:"Times New Roman"; 	mso-fareast-theme-font:minor-fareast; 	mso-hansi-font-family:Calibri; 	mso-hansi-theme-font:minor-latin; 	mso-bidi-font-family:"Times New Roman"; 	mso-bidi-theme-font:minor-bidi;} --> <!--[endif]--></p>
<p class="MsoNormal"><a name="_MailAutoSig"><span style="font-size: 12pt;">The distributed dictionary attack has been going on for five straight days.<span> </span></span></a><span style="font-size: 12pt;">By my informal estimates, there have been about 30,000 attempts to break in on our coyglen server.<br />
<!--[if !supportLineBreakNewLine]--><br />
<!--[endif]--></span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">After weighing options, I decided to restrict access by IP number.<span> </span>I was a bit gun-shy after Friday night’s mistake of trying to restrict by country of origin and subsequent onsite hard-reboot.</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">Below I have pasted the unique usernames and IP addresses used for valid SSH logins on coyglen in the last 30 days.<span> </span>I used the IP#s to create a list of valid computers and added them to the /etc/hosts.allow file.</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">After restarting the SSH service, I am delighted to see many connection-refusal messages.</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">If a customer cannot get access using SSH or SFTP (note: insecure FTP is not supported on this server), then have them send you an e-mail message.<span> </span>We will open up their e-mail headers and look at the IP#, then add it to the allowed list.</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">I have not applied this solution to our other servers, but will if needed.<span> </span>If someone controls a “zombie” network, then it is trivial to direct attacks like this trying to find insecure hosting servers.<span> </span>I expect more of these to happen in the future.</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;">Matt</span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">[root@coyglen log]# fgrep &#8220;Accepted&#8221; secure* | awk &#8216;{print $9}&#8217; | sort | uniq</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">cogentex</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">csg</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">csg2</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">dgreen</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">elimilice</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">gorges</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">gorges-blog</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">gorges-test</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">healthytest</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">myteambook</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">napoli</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">netway-dev</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">ngrown</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">paramount</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">pmpauthors</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">qbuilder</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">retweeter</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">root</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">royaltystat</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">sales.wwm</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">stablecom.sta</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">stlnps</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">userfinity</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">valmontgod</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;"> </span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">[root@coyglen log]# fgrep &#8220;Accepted&#8221; secure* | awk &#8216;{print $11}&#8217; | sort | uniq</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">166.137.136.169</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">166.77.6.4</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">184.74.139.251</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">192.168.1.3</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">208.105.225.93</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">24.58.62.167</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">24.58.67.78</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">24.59.191.200</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">38.108.102.253</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">64.57.177.68</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">64.57.178.15</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">66.180.184.17</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">67.244.55.108</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">67.246.70.140</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">67.255.18.108</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">67.255.3.244</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">67.255.5.155</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">69.205.224.88</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">70.94.56.13</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">71.181.154.195</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">71.181.238.165</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">71.183.96.7</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">72.43.91.102</span></p>
<p class="MsoNormal"><span style="font-size: 12pt; font-family: &amp;amp;amp; color: #365f91;">74.66.27.169</span></p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2010/06/distributed-dictionary-attack-solutions/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Android Two-Dimensional ScrollView</title>
		<link>http://blog.GORGES.us/2010/06/android-two-dimensional-scrollview/</link>
		<comments>http://blog.GORGES.us/2010/06/android-two-dimensional-scrollview/#comments</comments>
		<pubDate>Wed, 02 Jun 2010 17:32:57 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Mobile Development]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[mobile phone]]></category>
		<category><![CDATA[mobility]]></category>
		<category><![CDATA[scrollview]]></category>
		<category><![CDATA[two-dimensional]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=240</guid>
		<description><![CDATA[We developed a two-dimensional scrolling view for the Android platform, which is missing among the available base classes.  This solution was derived from combining the ScrollView and HorizontalScrollView base classes.]]></description>
			<content:encoded><![CDATA[<p>Recently, while developing mobile applications for the Android platform, we were pleasantly surprised to see how much the internal display classes work like Java&#8217;s Swing components.  The online Android documentation was good, and there were plenty of available example apps to help speed us along.</p>
<p>However there is a glaring limitation:  there are base classes for horizontal scrollviews and vertical scrollviews, but not one where one can scroll in two dimensions at the same time.</p>
<p>It would be a challenge to write a two-dimensional scrollview class from scratch.  Luckily for us, the entire Android platform is open-source, so we had access to both the vertical and horizontal scrollview source code.  After a few hours of work we had created a new TwoDScrollView class.</p>
<p>First, here is a disclaimer: this class has been &#8220;munged&#8221; together and is not bullet-proof for a true generalized solution.  For example the methods that handle sub-view focusing have not been tested, and there are some nuances about how to prioritize focused sub-views in two-dimensions.  I have a fully-stripped version of this class without sub-view focusing or key events that I&#8217;d be happy to share upon request.</p>
<p>Without further ado, here is the new TwoDScrollView class:</p>
<pre class="brush: java; collapse: true; light: false; toolbar: true;">
/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
 * 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 &quot;AS IS&quot; 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.
 */
/*
 * Revised 5/19/2010 by GORGES
 * Now supports two-dimensional view scrolling
 * http://GORGES.us
 */

package us.gorges.my_package;

import java.util.List;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;

/**
 * Layout container for a view hierarchy that can be scrolled by the user,
 * allowing it to be larger than the physical display.  A TwoDScrollView
 * is a {@link FrameLayout}, meaning you should place one child in it
 * containing the entire contents to scroll; this child may itself be a layout
 * manager with a complex hierarchy of objects.  A child that is often used
 * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
 * array of top-level items that the user can scroll through.
 *
 * &lt;p&gt;The {@link TextView} class also
 * takes care of its own scrolling, so does not require a TwoDScrollView, but
 * using the two together is possible to achieve the effect of a text view
 * within a larger container.
 */
public class TwoDScrollView extends FrameLayout {
 static final int ANIMATED_SCROLL_GAP = 250;
 static final float MAX_SCROLL_FACTOR = 0.5f;

 private long mLastScroll;

 private final Rect mTempRect = new Rect();
 private Scroller mScroller;

 /**
 * Flag to indicate that we are moving focus ourselves. This is so the
 * code that watches for focus changes initiated outside this TwoDScrollView
 * knows that it does not have to do anything.
 */
 private boolean mTwoDScrollViewMovedFocus;

 /**
 * Position of the last motion event.
 */
 private float mLastMotionY;
 private float mLastMotionX;

 /**
 * True when the layout has changed but the traversal has not come through yet.
 * Ideally the view hierarchy would keep track of this for us.
 */
 private boolean mIsLayoutDirty = true;

 /**
 * The child to give focus to in the event that a child has requested focus while the
 * layout is dirty. This prevents the scroll from being wrong if the child has not been
 * laid out before requesting focus.
 */
 private View mChildToScrollTo = null;

 /**
 * True if the user is currently dragging this TwoDScrollView around. This is
 * not the same as 'is being flinged', which can be checked by
 * mScroller.isFinished() (flinging begins when the user lifts his finger).
 */
 private boolean mIsBeingDragged = false;

 /**
 * Determines speed during touch scrolling
 */
 private VelocityTracker mVelocityTracker;

 /**
 * Whether arrow scrolling is animated.
 */
 private int mTouchSlop;
 private int mMinimumVelocity;
 private int mMaximumVelocity;

 public TwoDScrollView(Context context) {
   super(context);
   initTwoDScrollView();
 }

 public TwoDScrollView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initTwoDScrollView();
 }

 public TwoDScrollView(Context context, AttributeSet attrs, int defStyle) {
   super(context, attrs, defStyle);
   initTwoDScrollView();
 }

 @Override
 protected float getTopFadingEdgeStrength() {
   if (getChildCount() == 0) {
     return 0.0f;
   }
   final int length = getVerticalFadingEdgeLength();
   if (getScrollY() &lt; length) {
     return getScrollY() / (float) length;
   }
   return 1.0f;
 }

 @Override
 protected float getBottomFadingEdgeStrength() {
   if (getChildCount() == 0) {
     return 0.0f;
   }
   final int length = getVerticalFadingEdgeLength();
   final int bottomEdge = getHeight() - getPaddingBottom();
   final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
   if (span &lt; length) {
     return span / (float) length;
   }
   return 1.0f;
 }

 @Override
 protected float getLeftFadingEdgeStrength() {
   if (getChildCount() == 0) {
     return 0.0f;
   }
   final int length = getHorizontalFadingEdgeLength();
   if (getScrollX() &lt; length) {
     return getScrollX() / (float) length;
   }
   return 1.0f;
 }

 @Override
 protected float getRightFadingEdgeStrength() {
   if (getChildCount() == 0) {
     return 0.0f;
   }
   final int length = getHorizontalFadingEdgeLength();
   final int rightEdge = getWidth() - getPaddingRight();
   final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
   if (span &lt; length) {
     return span / (float) length;
   }
   return 1.0f;
 }

 /**
 * @return The maximum amount this scroll view will scroll in response to
 *   an arrow event.
 */
 public int getMaxScrollAmountVertical() {
   return (int) (MAX_SCROLL_FACTOR * getHeight());
 }
 public int getMaxScrollAmountHorizontal() {
   return (int) (MAX_SCROLL_FACTOR * getWidth());
 }

 private void initTwoDScrollView() {
   mScroller = new Scroller(getContext());
   setFocusable(true);
   setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
   setWillNotDraw(false);
   final ViewConfiguration configuration = ViewConfiguration.get(getContext());
   mTouchSlop = configuration.getScaledTouchSlop();
   mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
   mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 }

 @Override
 public void addView(View child) {
   if (getChildCount() &gt; 0) {
     throw new IllegalStateException(&quot;TwoDScrollView can host only one direct child&quot;);
   }
   super.addView(child);
 }

 @Override
 public void addView(View child, int index) {
   if (getChildCount() &gt; 0) {
     throw new IllegalStateException(&quot;TwoDScrollView can host only one direct child&quot;);
   }
   super.addView(child, index);
 }

 @Override
 public void addView(View child, ViewGroup.LayoutParams params) {
   if (getChildCount() &gt; 0) {
     throw new IllegalStateException(&quot;TwoDScrollView can host only one direct child&quot;);
   }
   super.addView(child, params);
 }

 @Override
 public void addView(View child, int index, ViewGroup.LayoutParams params) {
   if (getChildCount() &gt; 0) {
     throw new IllegalStateException(&quot;TwoDScrollView can host only one direct child&quot;);
   }
   super.addView(child, index, params);
 }

 /**
 * @return Returns true this TwoDScrollView can be scrolled
 */
 private boolean canScroll() {
   View child = getChildAt(0);
   if (child != null) {
     int childHeight = child.getHeight();
     int childWidth = child.getWidth();
     return (getHeight() &lt; childHeight + getPaddingTop() + getPaddingBottom()) ||
            (getWidth() &lt; childWidth + getPaddingLeft() + getPaddingRight());
   }
   return false;
 }

 @Override
 public boolean dispatchKeyEvent(KeyEvent event) {
   // Let the focused view and/or our descendants get the key first
   boolean handled = super.dispatchKeyEvent(event);
   if (handled) {
     return true;
   }
   return executeKeyEvent(event);
 }

 /**
 * You can call this function yourself to have the scroll view perform
 * scrolling from a key event, just as if the event had been dispatched to
 * it by the view hierarchy.
 *
 * @param event The key event to execute.
 * @return Return true if the event was handled, else false.
 */
 public boolean executeKeyEvent(KeyEvent event) {
   mTempRect.setEmpty();
   if (!canScroll()) {
     if (isFocused()) {
       View currentFocused = findFocus();
       if (currentFocused == this) currentFocused = null;
       View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, View.FOCUS_DOWN);
       return nextFocused != null &amp;&amp; nextFocused != this &amp;&amp; nextFocused.requestFocus(View.FOCUS_DOWN);
     }
     return false;
   }
   boolean handled = false;
   if (event.getAction() == KeyEvent.ACTION_DOWN) {
     switch (event.getKeyCode()) {
       case KeyEvent.KEYCODE_DPAD_UP:
         if (!event.isAltPressed()) {
           handled = arrowScroll(View.FOCUS_UP, false);
         } else {
           handled = fullScroll(View.FOCUS_UP, false);
         }
         break;
       case KeyEvent.KEYCODE_DPAD_DOWN:
         if (!event.isAltPressed()) {
           handled = arrowScroll(View.FOCUS_DOWN, false);
         } else {
           handled = fullScroll(View.FOCUS_DOWN, false);
         }
         break;
       case KeyEvent.KEYCODE_DPAD_LEFT:
         if (!event.isAltPressed()) {
           handled = arrowScroll(View.FOCUS_UP, true);
         } else {
           handled = fullScroll(View.FOCUS_UP, true);
         }
         break;
       case KeyEvent.KEYCODE_DPAD_RIGHT:
         if (!event.isAltPressed()) {
           handled = arrowScroll(View.FOCUS_DOWN, true);
         } else {
           handled = fullScroll(View.FOCUS_DOWN, true);
         }
         break;
     }
   }
   return handled;
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
   /*
   * This method JUST determines whether we want to intercept the motion.
   * If we return true, onMotionEvent will be called and we do the actual
   * scrolling there.
   *
   * Shortcut the most recurring case: the user is in the dragging
   * state and he is moving his finger.  We want to intercept this
   * motion.
   */
   final int action = ev.getAction();
   if ((action == MotionEvent.ACTION_MOVE) &amp;&amp; (mIsBeingDragged)) {
     return true;
   }
   if (!canScroll()) {
     mIsBeingDragged = false;
     return false;
   }
   final float y = ev.getY();
   final float x = ev.getX();
   switch (action) {
     case MotionEvent.ACTION_MOVE:
       /*
       * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
       * whether the user has moved far enough from his original down touch.
       */
       /*
       * Locally do absolute value. mLastMotionY is set to the y value
       * of the down event.
       */
       final int yDiff = (int) Math.abs(y - mLastMotionY);
       final int xDiff = (int) Math.abs(x - mLastMotionX);
       if (yDiff &gt; mTouchSlop || xDiff &gt; mTouchSlop) {
         mIsBeingDragged = true;
       }
       break;

     case MotionEvent.ACTION_DOWN:
       /* Remember location of down touch */
       mLastMotionY = y;
       mLastMotionX = x;

       /*
       * If being flinged and user touches the screen, initiate drag;
       * otherwise don't.  mScroller.isFinished should be false when
       * being flinged.
       */
       mIsBeingDragged = !mScroller.isFinished();
       break;

     case MotionEvent.ACTION_CANCEL:
     case MotionEvent.ACTION_UP:
       /* Release the drag */
       mIsBeingDragged = false;
       break;
   }

   /*
   * The only time we want to intercept motion events is if we are in the
   * drag mode.
   */
   return mIsBeingDragged;
 }

 @Override
 public boolean onTouchEvent(MotionEvent ev) {

   if (ev.getAction() == MotionEvent.ACTION_DOWN &amp;&amp; ev.getEdgeFlags() != 0) {
     // Don't handle edge touches immediately -- they may actually belong to one of our
     // descendants.
     return false;
   }

   if (!canScroll()) {
     return false;
   }

   if (mVelocityTracker == null) {
     mVelocityTracker = VelocityTracker.obtain();
   }
   mVelocityTracker.addMovement(ev);

   final int action = ev.getAction();
   final float y = ev.getY();
   final float x = ev.getX();

   switch (action) {
     case MotionEvent.ACTION_DOWN:
       /*
       * If being flinged and user touches, stop the fling. isFinished
       * will be false if being flinged.
       */
       if (!mScroller.isFinished()) {
         mScroller.abortAnimation();
       }

       // Remember where the motion event started
       mLastMotionY = y;
       mLastMotionX = x;
       break;
     case MotionEvent.ACTION_MOVE:
       // Scroll to follow the motion event
       int deltaX = (int) (mLastMotionX - x);
       int deltaY = (int) (mLastMotionY - y);
       mLastMotionX = x;
       mLastMotionY = y;

       if (deltaX &lt; 0) {
         if (getScrollX() &lt; 0) {
           deltaX = 0;
         }
       } else if (deltaX &gt; 0) {
         final int rightEdge = getWidth() - getPaddingRight();
         final int availableToScroll = getChildAt(0).getRight() - getScrollX() - rightEdge;
         if (availableToScroll &gt; 0) {
           deltaX = Math.min(availableToScroll, deltaX);
         } else {
           deltaX = 0;
         }
       }
       if (deltaY &lt; 0) {
         if (getScrollY() &lt; 0) {
           deltaY = 0;
         }
       } else if (deltaY &gt; 0) {
         final int bottomEdge = getHeight() - getPaddingBottom();
         final int availableToScroll = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
         if (availableToScroll &gt; 0) {
           deltaY = Math.min(availableToScroll, deltaY);
         } else {
           deltaY = 0;
         }
       }
       if (deltaY != 0 || deltaX != 0)
         scrollBy(deltaX, deltaY);
       break;
     case MotionEvent.ACTION_UP:
         final VelocityTracker velocityTracker = mVelocityTracker;
         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
         int initialXVelocity = (int) velocityTracker.getXVelocity();
         int initialYVelocity = (int) velocityTracker.getYVelocity();
         if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) &gt; mMinimumVelocity) &amp;&amp; getChildCount() &gt; 0) {
           fling(-initialXVelocity, -initialYVelocity);
         }
         if (mVelocityTracker != null) {
           mVelocityTracker.recycle();
           mVelocityTracker = null;
         }
   }
   return true;
 }

 /**
  * Finds the next focusable component that fits in this View's bounds
  * (excluding fading edges) pretending that this View's top is located at
  * the parameter top.
  *
  * @param topFocus           look for a candidate is the one at the top of the bounds
  *                           if topFocus is true, or at the bottom of the bounds if topFocus is
  *                           false
  * @param top                the top offset of the bounds in which a focusable must be
  *                           found (the fading edge is assumed to start at this position)
  * @param preferredFocusable the View that has highest priority and will be
  *                           returned if it is within my bounds (null is valid)
  * @return the next focusable component in the bounds or null if none can be
  *         found
  */
 private View findFocusableViewInMyBounds(final boolean topFocus, final int top, final boolean leftFocus, final int left, View preferredFocusable) {
   /*
   * The fading edge's transparent side should be considered for focus
   * since it's mostly visible, so we divide the actual fading edge length
   * by 2.
   */
   final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2;
   final int topWithoutFadingEdge = top + verticalFadingEdgeLength;
   final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength;
   final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
   final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength;
   final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength;

   if ((preferredFocusable != null)
     &amp;&amp; (preferredFocusable.getTop() &lt; bottomWithoutFadingEdge)
     &amp;&amp; (preferredFocusable.getBottom() &gt; topWithoutFadingEdge)
     &amp;&amp; (preferredFocusable.getLeft() &lt; rightWithoutFadingEdge)
     &amp;&amp; (preferredFocusable.getRight() &gt; leftWithoutFadingEdge)) {
     return preferredFocusable;
   }
   return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge, leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge);
 }

 /**
 * Finds the next focusable component that fits in the specified bounds.
 * &lt;/p&gt;
 *
 * @param topFocus look for a candidate is the one at the top of the bounds
 *                 if topFocus is true, or at the bottom of the bounds if topFocus is
 *                 false
 * @param top      the top offset of the bounds in which a focusable must be
 *                 found
 * @param bottom   the bottom offset of the bounds in which a focusable must
 *                 be found
 * @return the next focusable component in the bounds or null if none can
 *         be found
 */
 private View findFocusableViewInBounds(boolean topFocus, int top, int bottom, boolean leftFocus, int left, int right) {
   List&lt;View&gt; focusables = getFocusables(View.FOCUS_FORWARD);
   View focusCandidate = null;

   /*
   * A fully contained focusable is one where its top is below the bound's
   * top, and its bottom is above the bound's bottom. A partially
   * contained focusable is one where some part of it is within the
   * bounds, but it also has some part that is not within bounds.  A fully contained
   * focusable is preferred to a partially contained focusable.
   */
   boolean foundFullyContainedFocusable = false;

   int count = focusables.size();
   for (int i = 0; i &lt; count; i++) {
     View view = focusables.get(i);
     int viewTop = view.getTop();
     int viewBottom = view.getBottom();
     int viewLeft = view.getLeft();
     int viewRight = view.getRight();

     if (top &lt; viewBottom &amp;&amp; viewTop &lt; bottom &amp;&amp; left &lt; viewRight &amp;&amp; viewLeft &lt; right) {
       /*
       * the focusable is in the target area, it is a candidate for
       * focusing
       */
       final boolean viewIsFullyContained = (top &lt; viewTop) &amp;&amp; (viewBottom &lt; bottom) &amp;&amp; (left &lt; viewLeft) &amp;&amp; (viewRight &lt; right);
       if (focusCandidate == null) {
         /* No candidate, take this one */
         focusCandidate = view;
         foundFullyContainedFocusable = viewIsFullyContained;
       } else {
         final boolean viewIsCloserToVerticalBoundary =
           (topFocus &amp;&amp; viewTop &lt; focusCandidate.getTop()) ||
           (!topFocus &amp;&amp; viewBottom &gt; focusCandidate.getBottom());
         final boolean viewIsCloserToHorizontalBoundary =
           (leftFocus &amp;&amp; viewLeft &lt; focusCandidate.getLeft()) ||
           (!leftFocus &amp;&amp; viewRight &gt; focusCandidate.getRight());
         if (foundFullyContainedFocusable) {
           if (viewIsFullyContained &amp;&amp; viewIsCloserToVerticalBoundary &amp;&amp; viewIsCloserToHorizontalBoundary) {
             /*
              * We're dealing with only fully contained views, so
              * it has to be closer to the boundary to beat our
              * candidate
              */
             focusCandidate = view;
           }
         } else {
           if (viewIsFullyContained) {
             /* Any fully contained view beats a partially contained view */
             focusCandidate = view;
             foundFullyContainedFocusable = true;
           } else if (viewIsCloserToVerticalBoundary &amp;&amp; viewIsCloserToHorizontalBoundary) {
             /*
              * Partially contained view beats another partially
              * contained view if it's closer
              */
             focusCandidate = view;
           }
         }
       }
     }
   }
   return focusCandidate;
 }

 /**
  * &lt;p&gt;Handles scrolling in response to a &quot;home/end&quot; shortcut press. This
  * method will scroll the view to the top or bottom and give the focus
  * to the topmost/bottommost component in the new visible area. If no
  * component is a good candidate for focus, this scrollview reclaims the
  * focus.&lt;/p&gt;
  *
  * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
  *                  to go the top of the view or
  *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
  * @return true if the key event is consumed by this method, false otherwise
  */
 public boolean fullScroll(int direction, boolean horizontal) {
   if (!horizontal) {
     boolean down = direction == View.FOCUS_DOWN;
     int height = getHeight();
     mTempRect.top = 0;
     mTempRect.bottom = height;
     if (down) {
       int count = getChildCount();
       if (count &gt; 0) {
         View view = getChildAt(count - 1);
         mTempRect.bottom = view.getBottom();
         mTempRect.top = mTempRect.bottom - height;
       }
     }
     return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0);
   } else {
     boolean right = direction == View.FOCUS_DOWN;
     int width = getWidth();
     mTempRect.left = 0;
     mTempRect.right = width;
     if (right) {
       int count = getChildCount();
       if (count &gt; 0) {
         View view = getChildAt(count - 1);
         mTempRect.right = view.getBottom();
         mTempRect.left = mTempRect.right - width;
       }
     }
     return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom);
   }
 }

 /**
  * &lt;p&gt;Scrolls the view to make the area defined by &lt;code&gt;top&lt;/code&gt; and
  * &lt;code&gt;bottom&lt;/code&gt; visible. This method attempts to give the focus
  * to a component visible in this area. If no component can be focused in
  * the new visible area, the focus is reclaimed by this scrollview.&lt;/p&gt;
  *
  * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
  *                  to go upward
  *                  {@link android.view.View#FOCUS_DOWN} to downward
  * @param top       the top offset of the new area to be made visible
  * @param bottom    the bottom offset of the new area to be made visible
  * @return true if the key event is consumed by this method, false otherwise
  */
 private boolean scrollAndFocus(int directionY, int top, int bottom, int directionX, int left, int right) {
   boolean handled = true;
   int height = getHeight();
   int containerTop = getScrollY();
   int containerBottom = containerTop + height;
   boolean up = directionY == View.FOCUS_UP;
   int width = getWidth();
   int containerLeft = getScrollX();
   int containerRight = containerLeft + width;
   boolean leftwards = directionX == View.FOCUS_UP;
   View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right);
   if (newFocused == null) {
     newFocused = this;
   }
   if ((top &gt;= containerTop &amp;&amp; bottom &lt;= containerBottom) || (left &gt;= containerLeft &amp;&amp; right &lt;= containerRight)) {
     handled = false;
   } else {
     int deltaY = up ? (top - containerTop) : (bottom - containerBottom);
     int deltaX = leftwards ? (left - containerLeft) : (right - containerRight);
     doScroll(deltaX, deltaY);
   }
   if (newFocused != findFocus() &amp;&amp; newFocused.requestFocus(directionY)) {
     mTwoDScrollViewMovedFocus = true;
     mTwoDScrollViewMovedFocus = false;
   }
   return handled;
 }

 /**
  * Handle scrolling in response to an up or down arrow click.
  *
  * @param direction The direction corresponding to the arrow key that was
  *                  pressed
  * @return True if we consumed the event, false otherwise
  */
 public boolean arrowScroll(int direction, boolean horizontal) {
   View currentFocused = findFocus();
   if (currentFocused == this) currentFocused = null;
   View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
   final int maxJump = horizontal ? getMaxScrollAmountHorizontal() : getMaxScrollAmountVertical();

   if (!horizontal) {
     if (nextFocused != null) {
       nextFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(nextFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
       doScroll(0, scrollDelta);
       nextFocused.requestFocus(direction);
     } else {
       // no new focus
       int scrollDelta = maxJump;
       if (direction == View.FOCUS_UP &amp;&amp; getScrollY() &lt; scrollDelta) {
         scrollDelta = getScrollY();
       } else if (direction == View.FOCUS_DOWN) {
         if (getChildCount() &gt; 0) {
           int daBottom = getChildAt(0).getBottom();
           int screenBottom = getScrollY() + getHeight();
           if (daBottom - screenBottom &lt; maxJump) {
             scrollDelta = daBottom - screenBottom;
           }
         }
       }
       if (scrollDelta == 0) {
         return false;
       }
       doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
     }
   } else {
     if (nextFocused != null) {
       nextFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(nextFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
       doScroll(scrollDelta, 0);
       nextFocused.requestFocus(direction);
     } else {
       // no new focus
       int scrollDelta = maxJump;
       if (direction == View.FOCUS_UP &amp;&amp; getScrollY() &lt; scrollDelta) {
         scrollDelta = getScrollY();
       } else if (direction == View.FOCUS_DOWN) {
         if (getChildCount() &gt; 0) {
           int daBottom = getChildAt(0).getBottom();
           int screenBottom = getScrollY() + getHeight();
           if (daBottom - screenBottom &lt; maxJump) {
             scrollDelta = daBottom - screenBottom;
           }
         }
       }
       if (scrollDelta == 0) {
         return false;
       }
       doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0);
     }
   }
   return true;
 }

 /**
  * Smooth scroll by a Y delta
  *
  * @param delta the number of pixels to scroll by on the Y axis
  */
 private void doScroll(int deltaX, int deltaY) {
   if (deltaX != 0 || deltaY != 0) {
     smoothScrollBy(deltaX, deltaY);
   }
 }

 /**
  * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
  *
  * @param dx the number of pixels to scroll by on the X axis
  * @param dy the number of pixels to scroll by on the Y axis
  */
 public final void smoothScrollBy(int dx, int dy) {
   long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
   if (duration &gt; ANIMATED_SCROLL_GAP) {
     mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
     awakenScrollBars(mScroller.getDuration());
     invalidate();
   } else {
     if (!mScroller.isFinished()) {
       mScroller.abortAnimation();
     }
     scrollBy(dx, dy);
   }
   mLastScroll = AnimationUtils.currentAnimationTimeMillis();
 }

 /**
  * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
  *
  * @param x the position where to scroll on the X axis
  * @param y the position where to scroll on the Y axis
  */
 public final void smoothScrollTo(int x, int y) {
   smoothScrollBy(x - getScrollX(), y - getScrollY());
 }

 /**
  * &lt;p&gt;The scroll range of a scroll view is the overall height of all of its
  * children.&lt;/p&gt;
  */
 @Override
 protected int computeVerticalScrollRange() {
   int count = getChildCount();
   return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
 }
 @Override
 protected int computeHorizontalScrollRange() {
   int count = getChildCount();
   return count == 0 ? getWidth() : (getChildAt(0)).getRight();
 }

 @Override
 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
   ViewGroup.LayoutParams lp = child.getLayoutParams();
   int childWidthMeasureSpec;
   int childHeightMeasureSpec;

   childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width);
   childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

   child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 }

 @Override
 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
   final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
   final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
   getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
   final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

   child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 }

 @Override
 public void computeScroll() {
   if (mScroller.computeScrollOffset()) {
     // This is called at drawing time by ViewGroup.  We don't want to
     // re-show the scrollbars at this point, which scrollTo will do,
     // so we replicate most of scrollTo here.
     //
     //         It's a little odd to call onScrollChanged from inside the drawing.
     //
     //         It is, except when you remember that computeScroll() is used to
     //         animate scrolling. So unless we want to defer the onScrollChanged()
     //         until the end of the animated scrolling, we don't really have a
     //         choice here.
     //
     //         I agree.  The alternative, which I think would be worse, is to post
     //         something and tell the subclasses later.  This is bad because there
     //         will be a window where mScrollX/Y is different from what the app
     //         thinks it is.
     //
     int oldX = getScrollX();
     int oldY = getScrollY();
     int x = mScroller.getCurrX();
     int y = mScroller.getCurrY();
     if (getChildCount() &gt; 0) {
       View child = getChildAt(0);
       scrollTo(clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
       clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()));
     } else {
       scrollTo(x, y);
     }
     if (oldX != getScrollX() || oldY != getScrollY()) {
       onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
     }

     // Keep on drawing until the animation has finished.
     postInvalidate();
   }
 }

 /**
  * Scrolls the view to the given child.
  *
  * @param child the View to scroll to
  */
 private void scrollToChild(View child) {
   child.getDrawingRect(mTempRect);
   /* Offset from child's local coordinates to TwoDScrollView coordinates */
   offsetDescendantRectToMyCoords(child, mTempRect);
   int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   if (scrollDelta != 0) {
     scrollBy(0, scrollDelta);
   }
 }

 /**
  * If rect is off screen, scroll just enough to get it (or at least the
  * first screen size chunk of it) on screen.
  *
  * @param rect      The rectangle.
  * @param immediate True to scroll immediately without animation
  * @return true if scrolling was performed
  */
 private boolean scrollToChildRect(Rect rect, boolean immediate) {
   final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
   final boolean scroll = delta != 0;
   if (scroll) {
     if (immediate) {
       scrollBy(0, delta);
     } else {
       smoothScrollBy(0, delta);
     }
   }
   return scroll;
 }

 /**
  * Compute the amount to scroll in the Y direction in order to get
  * a rectangle completely on the screen (or, if taller than the screen,
  * at least the first screen size chunk of it).
  *
  * @param rect The rect.
  * @return The scroll delta.
  */
 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
   if (getChildCount() == 0) return 0;
   int height = getHeight();
   int screenTop = getScrollY();
   int screenBottom = screenTop + height;
   int fadingEdge = getVerticalFadingEdgeLength();
   // leave room for top fading edge as long as rect isn't at very top
   if (rect.top &gt; 0) {
     screenTop += fadingEdge;
   }

   // leave room for bottom fading edge as long as rect isn't at very bottom
   if (rect.bottom &lt; getChildAt(0).getHeight()) {
     screenBottom -= fadingEdge;
   }
   int scrollYDelta = 0;
   if (rect.bottom &gt; screenBottom &amp;&amp; rect.top &gt; screenTop) {
     // need to move down to get it in view: move down just enough so
     // that the entire rectangle is in view (or at least the first
     // screen size chunk).
     if (rect.height() &gt; height) {
       // just enough to get screen size chunk on
       scrollYDelta += (rect.top - screenTop);
     } else {
       // get entire rect at bottom of screen
       scrollYDelta += (rect.bottom - screenBottom);
     }

     // make sure we aren't scrolling beyond the end of our content
     int bottom = getChildAt(0).getBottom();
     int distanceToBottom = bottom - screenBottom;
     scrollYDelta = Math.min(scrollYDelta, distanceToBottom);

   } else if (rect.top &lt; screenTop &amp;&amp; rect.bottom &lt; screenBottom) {
     // need to move up to get it in view: move up just enough so that
     // entire rectangle is in view (or at least the first screen
     // size chunk of it).

     if (rect.height() &gt; height) {
       // screen size chunk
       scrollYDelta -= (screenBottom - rect.bottom);
     } else {
       // entire rect at top
       scrollYDelta -= (screenTop - rect.top);
     }

     // make sure we aren't scrolling any further than the top our content
     scrollYDelta = Math.max(scrollYDelta, -getScrollY());
   }
   return scrollYDelta;
 }

 @Override
 public void requestChildFocus(View child, View focused) {
   if (!mTwoDScrollViewMovedFocus) {
     if (!mIsLayoutDirty) {
       scrollToChild(focused);
     } else {
       // The child may not be laid out yet, we can't compute the scroll yet
       mChildToScrollTo = focused;
     }
   }
   super.requestChildFocus(child, focused);
 }

 /**
  * When looking for focus in children of a scroll view, need to be a little
  * more careful not to give focus to something that is scrolled off screen.
  *
  * This is more expensive than the default {@link android.view.ViewGroup}
  * implementation, otherwise this behavior might have been made the default.
  */
 @Override
 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
   // convert from forward / backward notation to up / down / left / right
   // (ugh).
   if (direction == View.FOCUS_FORWARD) {
     direction = View.FOCUS_DOWN;
   } else if (direction == View.FOCUS_BACKWARD) {
     direction = View.FOCUS_UP;
   }

   final View nextFocus = previouslyFocusedRect == null ?
   FocusFinder.getInstance().findNextFocus(this, null, direction) :
   FocusFinder.getInstance().findNextFocusFromRect(this,
   previouslyFocusedRect, direction);

   if (nextFocus == null) {
     return false;
   }

   return nextFocus.requestFocus(direction, previouslyFocusedRect);
 }

 @Override
 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
   // offset into coordinate space of this scroll view
   rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
   return scrollToChildRect(rectangle, immediate);
 }

 @Override
 public void requestLayout() {
   mIsLayoutDirty = true;
   super.requestLayout();
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
   super.onLayout(changed, l, t, r, b);
   mIsLayoutDirty = false;
   // Give a child focus if it needs it
   if (mChildToScrollTo != null &amp;&amp; isViewDescendantOf(mChildToScrollTo, this)) {
     scrollToChild(mChildToScrollTo);
   }
   mChildToScrollTo = null;

   // Calling this with the present values causes it to re-clam them
   scrollTo(getScrollX(), getScrollY());
 }

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   super.onSizeChanged(w, h, oldw, oldh);

   View currentFocused = findFocus();
   if (null == currentFocused || this == currentFocused)
     return;

   // If the currently-focused view was visible on the screen when the
   // screen was at the old height, then scroll the screen to make that
   // view visible with the new screen height.
   currentFocused.getDrawingRect(mTempRect);
   offsetDescendantRectToMyCoords(currentFocused, mTempRect);
   int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   doScroll(scrollDeltaX, scrollDeltaY);
 }

 /**
  * Return true if child is an descendant of parent, (or equal to the parent).
  */
 private boolean isViewDescendantOf(View child, View parent) {
   if (child == parent) {
     return true;
   }

   final ViewParent theParent = child.getParent();
   return (theParent instanceof ViewGroup) &amp;&amp; isViewDescendantOf((View) theParent, parent);
 }

 /**
  * Fling the scroll view
  *
  * @param velocityY The initial velocity in the Y direction. Positive
  *                  numbers mean that the finger/curor is moving down the screen,
  *                  which means we want to scroll towards the top.
  */
 public void fling(int velocityX, int velocityY) {
   if (getChildCount() &gt; 0) {
     int height = getHeight() - getPaddingBottom() - getPaddingTop();
     int bottom = getChildAt(0).getHeight();
     int width = getWidth() - getPaddingRight() - getPaddingLeft();
     int right = getChildAt(0).getWidth();

     mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, bottom - height);

     final boolean movingDown = velocityY &gt; 0;
     final boolean movingRight = velocityX &gt; 0;

     View newFocused = findFocusableViewInMyBounds(movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus());
     if (newFocused == null) {
       newFocused = this;
     }

     if (newFocused != findFocus() &amp;&amp; newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
       mTwoDScrollViewMovedFocus = true;
       mTwoDScrollViewMovedFocus = false;
     }

     awakenScrollBars(mScroller.getDuration());
     invalidate();
   }
 }

 /**
  * {@inheritDoc}
  *
  * &lt;p&gt;This version also clamps the scrolling to the bounds of our child.
  */
 public void scrollTo(int x, int y) {
   // we rely on the fact the View.scrollBy calls scrollTo.
   if (getChildCount() &gt; 0) {
     View child = getChildAt(0);
     x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
     y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
     if (x != getScrollX() || y != getScrollY()) {
       super.scrollTo(x, y);
     }
   }
 }

 private int clamp(int n, int my, int child) {
   if (my &gt;= child || n &lt; 0) {
     /* my &gt;= child is this case:
      *                    |--------------- me ---------------|
      *     |------ child ------|
      * or
      *     |--------------- me ---------------|
      *            |------ child ------|
      * or
      *     |--------------- me ---------------|
      *                                  |------ child ------|
      *
      * n &lt; 0 is this case:
      *     |------ me ------|
      *                    |-------- child --------|
      *     |-- mScrollX --|
      */
     return 0;
   }
   if ((my+n) &gt; child) {
     /* this case:
      *                    |------ me ------|
      *     |------ child ------|
      *     |-- mScrollX --|
      */
     return child-my;
   }
   return n;
 }
}
&lt;pre&gt;</pre>
<p>In hindsight I think I know why two-dimensioning scrolling is not inherently included:  it is a memory hog.  There is an emphasis for beautiful graphics in the framework to achieve the best user experience; examples of this emphasis are true-color (24-bit) pixels, alpha transparency channel support, smooth scrolling, and display caching.  However the Android system restricts an application to 16MB of heap memory.  This limit is met quickly for a large cached two-dimensional scrolled view &#8211; note that 2,000 by 2,000 pixels times 4 bytes/pixel is 16MB.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2010/06/android-two-dimensional-scrollview/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Asterisk and Cisco VOIP Phones</title>
		<link>http://blog.GORGES.us/2010/04/asterisk-and-cisco-voip-phones/</link>
		<comments>http://blog.GORGES.us/2010/04/asterisk-and-cisco-voip-phones/#comments</comments>
		<pubDate>Sat, 03 Apr 2010 13:05:00 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Asterisk]]></category>
		<category><![CDATA[cisco]]></category>
		<category><![CDATA[CP-7935]]></category>
		<category><![CDATA[Polycom]]></category>
		<category><![CDATA[Trixbox]]></category>
		<category><![CDATA[voip phones]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=185</guid>
		<description><![CDATA[This post contains step-by-step directions for configuring a CISCO VOIP phone into Trixbox version 2.8.  Trixbox had to be upgraded to support SCCP protocol.]]></description>
			<content:encoded><![CDATA[<p>A few years ago we needed an office phone system.  Having a limited budget and being a tech-head, I decided to deploy Asterisk, an open-source PBX solution.  The outlays were minimal since we only needed a telephony card (Digium TDM400P), several VOIP  phones (GrandStream 2000 &#8211; hello, eBay!), and an old PC.  We chose to install Trixbox, which is based on Asterisk and promotes itself as easier to install than Asterisk.</p>
<p>After perhaps too much futzing, we ended up with a small business phone system without any monthly PBX charges other than the analog phone lines from the phone company.  We had an automated phone directory, the phones worked as intercoms, voice mail turned into attached WAV sound files send as e-mail.  I was able to add phone extensions easily.  I love Asterisk!</p>
<p>Perhaps the success went to my head, since I set my sights on a fancy conference room phone for our growing business.  I bought a used Cisco Polycom CP-7935 phone for 1/3rd the price of a new device.  I&#8217;ll admit it &#8211; I&#8217;m an amateur at telephony and didn&#8217;t know the difference between SIP and SCCP.  It turns out that Trixbox only supports SIP extensions by default, and this conference room phone requires SCCP channel protocol.</p>
<p>So finally I am coming to the purpose of this post &#8211; simple instructions on to hook up a Cisco VOIP phone (that only uses SCCP protocol) to Trixbox version 2.8.  The following instructions were gleaned from some Google searches, and I am summarizing them since no one had simple steps online for the most-recent Trixbox version.</p>
<p>First we need to install some packages and retrieve the most-recent version of an SCCP solution from SourceForge.  Do the following commands signed in as root user on your Trixbox server:</p>
<pre class="brush: bash;">
# yum install asterisk16-devel gcc subversion
# cd /usr/local/
# wget http://sourceforge.net/projects/chan-sccp-b/files/chan-sccp-b/v2-based_20090602/chan_sccp-b_20090602.tar.gz/download
# tar xvfz chan_sccp-b_20090602.tar.gz
# cd /usr/local/chan_sccp-b_20090602
# make
</pre>
<p>If you perform the above steps, you will see that the make operation will not work without modifying some source code.  Thanks to a posting (<a href="http://lostentropy.com/2009/09/28/making-chan_sccp-build-with-asterisk-1-6/">http://lostentropy.com/2009/09/28/making-chan_sccp-build-with-asterisk-1-6/</a>) I learned that I have to change a reference to a constant from CS_AST_CONTROL_T38 to CS_AST_CONTROL_T38_PARAMETERS.  Make this change to the file /usr/local/chan_sccp-b_20090602/sccp_pbx.c on line 587 using your favorite text editor.</p>
<pre class="brush: bash;">
# make
# make install
# amportal restart
</pre>
<p>Now comes the hard part.  The CP-7935 gets its provisioning file from a TFTP server.  Reset the CP-7935 to its default factor settings.  Next use the device menu to set the TFTP server to your Trixbox IP address (follow steps in the manual from the Cisco website).  The Trixbox should have its TFTP service activated by default; for my server the TFTP directory on the server is /tftpboot/.</p>
<p>Monitor the TFTP log file (/var/log/atftp.log) while you reboot the CP-7935.  You should see a request in the format &#8220;SEP#.cnf.xml&#8221; in the log file, where the &#8220;#&#8221; is the MAC address of the CP-7935.  Now create the following file /tftpboot/SEP#.cnf.xml (mine is /tftpboot/SEP00e0752442c5.cnf.xml) with this content and replacing the TRIXBOX_IP_ADDRESS with your Trixbox server address:</p>
<pre class="brush: xml;">
&lt;Default&gt;
&lt;callManagerGroup&gt;
&lt;members&gt;
&lt;member priority=&quot;0&quot;&gt;
&lt;callManager&gt;
&lt;ports&gt;
&lt;ethernetPhonePort&gt;2000&lt;/ethernetPhonePort&gt;
&lt;mgcpPorts&gt;
&lt;listen&gt;2427&lt;/listen&gt;
&lt;keepAlive&gt;2428&lt;/keepAlive&gt;
&lt;/mgcpPorts&gt;
&lt;/ports&gt;
&lt;processNodeName&gt;TRIXBOX_IP_ADDRESS&lt;/processNodeName&gt;
&lt;/callManager&gt;
&lt;/member&gt;
&lt;/members&gt;
&lt;/callManagerGroup&gt;
&lt;authenticationURL&gt;&lt;/authenticationURL&gt;
&lt;loadInformation&lt;/loadInformation&gt;
&lt;directoryURL&gt;&lt;/directoryURL&gt;
&lt;idleURL&gt;&lt;/idleURL&gt;
&lt;informationURL&gt;&lt;/informationURL&gt;
&lt;messagesURL&gt;&lt;/messagesURL&gt;
&lt;servicesURL&gt;&lt;/servicesURL&gt;
&lt;versionStamp&gt;{Apr 03 2010 12:00:00}&lt;/versionStamp&gt;
&lt;/Default&gt;
</pre>
<p>The last configuration entry tag for &lt;versionStamp&gt; is important since it is used by the device to determine if the settings have changed.  Update this versionStamp value to a later date to force the device to reload the settings in the file.</p>
<p>Next we need to write the SCCP configuration file that Asterisk reads.  First make a backup of the existing file, and then we will replace it with one tailored for our solution.</p>
<pre class="brush: bash;">
# mv /etc/asterisk/sccp.conf /etc/asterisk/sccp.conf.bak
</pre>
<p>Here are the new contents for the file /etc/asterisk/sccp.conf, and remember to replace the all-capital letter phrases but the specifics of your setup, for example TRIXBOX_SERVER_IP_ADDRESS is replace by the IP address of your trixbox, and the SEP00e0752442c5 with the string &#8220;SEP&#8221; and the MAC address of your Cisco phone.  Our phone model is 7935, so you will also need to change this to your phone type.</p>
<pre class="brush: bash; collapse: true; light: false; toolbar: true;">
[general]
servername = trixbox
keepalive = 60
debug = 1
context = from-internal
dateFormat = M/D/YA
bindaddr = TRIXBOX_SERVER_IP_ADDRESS
port = 2000
disallow=all
;allow=alaw
allow=ulaw
firstdigittimeout = 16
digittimeout = 8
digittimeoutchar = #
autoanswer_ring_time = 1
autoanswer_tone = 0x32
remotehangup_tone = 0x32
transfer_tone = 0
callwaiting_tone = 0x2d
musicclass=default
language=en
deny=0.0.0.0/0.0.0.0
permit=TRIXBOX_SERVER_IP_ADDRESS/255.255.255.0
localnet = 192.168.93.0/255.255.255.0
dnd = on
rtptos = 184
echocancel = on
silencesuppression = off
trustphoneip = no
tos = 0x68
private = on
mwilamp = on
mwioncall = on
blindtransferindication = ring
cfwdall = on
cfwdbusy = on
[devices]
type = 7935
autologin = CONFERENCE_PHONE_EXTENSION
description = Phone7935
keepalive = 60
transfer = on
park = on
cfwdall = on
cfwdbusy = on
dtmfmode = outband
imageversion = P00308000100
deny=0.0.0.0/0.0.0.0
permit=192.168.93.3/255.255.255.255
dnd = on
trustphoneip = no
private = on
mwilamp = on
mwioncall = on
device =&gt; SEP00e0752442c5
[lines]
id = CONFERENCE_PHONE_EXTENSION
pin = 1234
label = CONFERENCE_PHONE_EXTENSION
description = Conference Room
context = from-internal
incominglimit = 3
transfer = on
cid_name = Conference Room
cid_num = CONFERENCE_PHONE_EXTENSION
trnsfvm = 1
secondary_dialtone_digits = 9
secondary_dialtone_tone = 0x22
musicclass=default
language=en
rtptos = 184
echocancel = on
silencesuppression = on
line =&gt; CONFERENCE_PHONE_EXTENSION
</pre>
<p>We&#8217;re almost done.  Now restart the asterisk service:</p>
<pre class="brush: bash;">
# amportal restart
</pre>
<p>Finally create an extension in the Trixbox administrator interface, and make sure it matches the value of the CONFERENCE_ROOM_EXTENSION in the sccp.conf file above (we used &#8220;30&#8243;).  Do a hardware reboot of the conference room phone.</p>
<p>Missing from this posting is any explanation about firmware and provisioning.  Cisco sells firmware upgrades to their devices, and we bet that the existing firmware on the used conference phone device was sufficient.  Our bet paid off.</p>
<p>In summary, this solution may still take a few hours of work for your particular Cisco phone.  The payoff is grand &#8211; our office Trixbox solution saves us money daily by not having to lease or maintain an expensive private PBX system.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2010/04/asterisk-and-cisco-voip-phones/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>What Hosting Do I Need?</title>
		<link>http://blog.GORGES.us/2009/09/what-hosting-do-i-need/</link>
		<comments>http://blog.GORGES.us/2009/09/what-hosting-do-i-need/#comments</comments>
		<pubDate>Tue, 15 Sep 2009 13:00:23 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[System Administration]]></category>
		<category><![CDATA[hosting]]></category>
		<category><![CDATA[vps]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=136</guid>
		<description><![CDATA[Choosing a hosting service is important, and there are many choices to make.  Here are some tips to help you make your selection.]]></description>
			<content:encoded><![CDATA[<p>Choosing a hosting service is important, and there are many choices to make.  Here are some tips to help you make your selection.</p>
<p>The first step is to determine your business requirements.  The criteria should be reliability (or uptime), performance, support, and cost.  Try to estimate the cost of downtime, because that value should factor in your hosting decision.  If a day of downtime costs you thousands of dollars, then reliability is very important.</p>
<p>The cheapest hosting is to purchase an account on a shared server.  Your domain is one of perhaps hundreds or even thousands that vie for the server CPU, memory, and bandwidth.  If your site is slow, it may be difficult or even impossible to diagnose why since the fault may be with another domain on the same server.</p>
<p>The next level up is a virtual private server (VPS).  In reality you are still sharing the server with other customers, but there are separations between these relatively-independent operating systems so they affect each other less if problems on one arise.  The term &#8220;cloud computing&#8221; is really just another name for using virtual private servers, although often the cloud computing control panels make it easy and fast to add and remove VPS units as your domain needs change.</p>
<p>If you want the whole server to yourself, then you can hosting on a dedicated server.  This is all about control &#8211; there are no other customers to contend with if you are the only one using the server.  Note that you may need an experienced system administrator to help if you are setting up your own dedicated server.</p>
<p>If your domain outgrows a dedicated server, then you have graduated to a cluster solution.  You will have new challenges regarding sharing session management and your database between multiple servers.  It should also be mentioned that cloud computing supports clustering with their VPS machines, which is cheaper than a custom-built clustered solution.</p>
<p>At Gorges, we offer <a href="http://www.GORGES.us/hosting">shared-server and dedicated-server hosting solutions</a> to our software development clients.  We have two co-location facilities that we use in Ithaca, New York, and our servers are monitored constantly.  Since we do our own hosting, we can add software packages or customize the server configuration as-needed for our clients.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/09/what-hosting-do-i-need/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Securing Linux Web Servers</title>
		<link>http://blog.GORGES.us/2009/07/securing-linux-web-servers/</link>
		<comments>http://blog.GORGES.us/2009/07/securing-linux-web-servers/#comments</comments>
		<pubDate>Mon, 20 Jul 2009 13:00:17 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[System Administration]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[firewalls]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=130</guid>
		<description><![CDATA[We are often asked by our software development and hosting customers how we secure our servers.  We have several layers of security protection, and this blog posting will mention some that we implement. A firewall is used to only allow traffic to the outside world on a few of the TCP/UDP ports.  We obviously have [...]]]></description>
			<content:encoded><![CDATA[<p>We are often asked by our software development and hosting customers how we secure our servers.  We have several layers of security protection, and this blog posting will mention some that we implement.</p>
<p>A firewall is used to only allow traffic to the outside world on a few of the TCP/UDP ports.  We obviously have to allow web and e-mail users access to the server, but almost all other ports can be closed to prevent intrusion attempts.  On our newest servers we even prevent FTP and Telnet access, since those protocols rely on unencrypted packets which are easier to intercept and hijack.</p>
<p>Every day we have perhaps dozens of &#8220;dicitionary&#8221; attacks that try to gain e-mail or user account access.  A dictionary attack picks a user (for example &#8220;root&#8221; or &#8220;john&#8221;) and then goes through a long, long list of possible passwords.  We use two packages Fail2Ban and DenyHosts that monitor our log files looking for dictionary attacks; if found, the originating computer is banned from accessing our servers.</p>
<p>When we develop online shopping solutions, we choose to not store credit card numbers online.  We securely pass this information to the credit card processing vendor, and then we only record the order information and the payment confirmation number.  For some web sites with user accounts, we encrypt the user account passwords, therefore gaining access to our user password list would still not result in someone gaining access to their online account.</p>
<p>Some of our hosting customers are concerned about unencrypted web traffic.  We occasionally add a feature that automatically forwards a web page inquiry from non-SSL to SSL mode, which means it forward to a page starting with &#8220;https://&#8221; thus all traffic is encrypted between our server and each web browser client.</p>
<p>We also have logging records and constant monitoring to help us detect intrusion attempts and help us implement even better security measures.  &#8220;Tripwire&#8221; software can also alert us when certain files are modified.</p>
<p>Do these basic measures above make us impervious to hackers?  Alas, no.  On two occasions in the last five years we have had hackers penetrate one of our servers.  However no damage was done and we patched those specific holes quickly.  Security is a cat-and-mouse game, and we strive to stay one step ahead.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/07/securing-linux-web-servers/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>My MythTV Experiences</title>
		<link>http://blog.GORGES.us/2009/06/my-mythtv-experiences/</link>
		<comments>http://blog.GORGES.us/2009/06/my-mythtv-experiences/#comments</comments>
		<pubDate>Fri, 19 Jun 2009 13:00:12 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[HDHomeRun]]></category>
		<category><![CDATA[MythTV]]></category>
		<category><![CDATA[PVR]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=116</guid>
		<description><![CDATA[For about four years I have been using MythTV, which is a software package that records and plays back television shows.  Installing and configuring this package is not for the technology-challenged, and I have it working on a small Shuttle computer with a tv-tuner card and large hard disk.]]></description>
			<content:encoded><![CDATA[<p>This topic deviates a bit from our work-related blog, but a few liberties can be occasionally entertained.</p>
<p>For about four years I have been using MythTV, which is a software package that records and plays back television shows.  Installing and configuring this package is not for the technology-challenged, and I have it working on a small Shuttle computer with a tv-tuner card and large hard disk.</p>
<p>I rarely see live television since MythTV has the ability to find and remove commercials and I always prefer to watch television without commercials.  I have three young children, and I have recorded hundreds of educational shows for kids (e.g. Magic School Bus, Zula Patrol) all without commercials.</p>
<p>Although we do not yet own a high-definition television (I know &#8211; we&#8217;re in the dark ages), I purchased a HDHomeRun device that converts unencrypted digital television signals into an ethernet-based UDP stream.  Since all television cable providers must provide unencrypted access to local digital channels, I can now record and playback high-definition shows recorded in MythTV to my laptop.</p>
<p>And converting any show or movie to an iPhone or iPod Touch compatible movie is as simple as running a script and then loading it into iTunes for syncing.  Long car trips are more than tolerable for our family when we can just hand an iPod Touch to a child which has a Backyardigans or Thomas the Tank Engine show on it.  And no need for another remote control &#8211; I can control MythTV from my iPhone or wireless iPod Touch.</p>
<p>And here&#8217;s one of the best features:  there is no monthly fee that Time Warner, Comcast, and DISH charge for PVR rental.  I do pay $20/year for access to an online television schedule, so I guess my licensing cost is a whopping 5¢/day.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/06/my-mythtv-experiences/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Server Control Panel Comparison</title>
		<link>http://blog.GORGES.us/2009/05/server-control-panel-comparison/</link>
		<comments>http://blog.GORGES.us/2009/05/server-control-panel-comparison/#comments</comments>
		<pubDate>Fri, 22 May 2009 13:00:34 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[System Administration]]></category>
		<category><![CDATA[Control Panels]]></category>
		<category><![CDATA[CPanel]]></category>
		<category><![CDATA[Plesk]]></category>
		<category><![CDATA[VHCS2]]></category>
		<category><![CDATA[WebMin]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=112</guid>
		<description><![CDATA[Control panels are used to configure and maintain servers by both system administrators and users.  Years ago all the server settings for web, e-mail, name service, and other packages were done manually by system administrators.  Nowadays there are powerful control panel packages that configure the settings based on relatively simple interfaces.]]></description>
			<content:encoded><![CDATA[<p>Control panels are used to configure and maintain servers by both system administrators and users.  Years ago all the server settings for web, e-mail, name service, and other packages were done manually by system administrators.  Nowadays there are powerful control panel packages that configure the settings based on relatively simple interfaces.</p>
<p>We have experimented with several Control Panels for our Gorges servers.  This blog post is not intended to be an exhaustive list of all control panel software, but rather a summary of our own experiences.</p>
<p><strong>PLESK and CPANEL</strong></p>
<p>These two solutions are terrific, and they present the obscure settings needed to control web/e-mail/etc. packages in a meaningful way.  The biggest drawback is the price.  At Gorges we run our servers &#8220;lean&#8221; with relatively few customers per server; this maximizes the web page performance.  Adding a commercial control panel to our servers would be costly and our hosting fees would have to rise perhaps unacceptably.</p>
<p>We do not host all the web applications we develop, and we often work with Plesk and CPanel on customer-supplied servers as well as one of our own.  These packages work, but for hosting companies to justify their cost they either overload the servers with domains or use virtual machines to squeeze more clients onto each server box.  You get what you pay for &#8211; and it can be truly frustrating when your domain is hosted on a server that has other customer domains saturating the bandwidth and processors.</p>
<p><strong>WEBMIN</strong></p>
<p>Webmin is perhaps the simplest of control panels, and basically just adds web page interfaces for packages.  We used this for a while, but the settings were so low-level that one had to be a system administrator to understand the screens, so the improvement was only marginal since most savvy sys-admins know the text interface already.  The companion package Usermin was perhaps more useful to the customer since it is for configuring e-mail accounts.</p>
<p><strong>VHCS2</strong></p>
<p>We have VHCS2 installed on most of our production servers.  This decision was made almost five years ago, and it took months to both learn all the nuances of how it works and to develop some custom solutions for important-but-missing features such as name service records and backups.  Although we liked VHCS2 at the time, work on this open-source package has apparently stopped, so it is stuck in time while better control panel software has surpassed the supported VHCS2 features.</p>
<p><strong>ISPCONFIG</strong></p>
<p>When we purchased several 64-bit quad-core servers in late 2007, we reviewed available control panel solutions.  The package ISPConfig was selected since it appeared much better than other control packages and was under open source license (i.e. free for us to install and use).</p>
<p>ISPConfig is not without problems, but we have extended this control panel solution with custom patches for grey-listing and spam filtering, propagating domain name service (DNS) records to our production name servers, and integrating it into our system-wide backup.</p>
<p><strong>SUMMARY</strong></p>
<p>Perhaps the biggest drawback of <strong>all</strong> control panel solutions is that it is not easy bypassing the panels and doing custom configurations for special-needs clients.  It&#8217;s pretty obvious that labor costs much more than hardware or bandwidth nowadays, so automating as much of the account setup and maintenance is the key to staying profitable.</p>
<p>As for us, we&#8217;ll keep using ISPConfig and passing the cost savings for hosting back to our customers.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/05/server-control-panel-comparison/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>When to Optimize Code for Performance</title>
		<link>http://blog.GORGES.us/2009/05/when-to-optimize/</link>
		<comments>http://blog.GORGES.us/2009/05/when-to-optimize/#comments</comments>
		<pubDate>Mon, 04 May 2009 13:00:09 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[performance]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=71</guid>
		<description><![CDATA[Of course writing bad or inefficient code is not a good way to develop code.  But spending time optimizing code before you know there is a problem can be a waste of time.  I value legible and readable code over unnecessarily-optimized code anyday.]]></description>
			<content:encoded><![CDATA[<p><strong>Question</strong>:  When should a web developer optimize?</p>
<p><strong>Answer</strong>:  When you have to improve the performance.</p>
<p>Of course writing bad or inefficient code is not a good way to develop code.  But spending time optimizing code before you know there is a problem can be a waste of time.  I value legible and readable code over unnecessarily-optimized code anyday.</p>
<p>If a web page or certain feature is too slow, then diagnosing exactly where to optimize is appropriate.  Here are some things to consider and test:</p>
<dl>
<dt>Is the bottleneck in the code or within a database call?</dt>
<dd>Perhaps the database needs to be indexed or normalized.</dd>
<dt>How many SQL calls are being performed?</dt>
<dd>Caching the database calls may solve this issue.</dd>
<dt>Is a database the best solution for storing all your information?</dt>
<dd>LDAP or memcache are two other options that may work best depending on what you are trying to do.</dd>
<dt>Are your files and images just too big?</dt>
<dd>Be sure the images are appropriately compressed, and that server-side compression for text files (HTML, CSS, JavaScript, XML) is activated.</dd>
<dt>Could any of the heavy logic code be pre-computed?</dt>
<dd>I recall one project (<a href="http://straightlineps.com">Straight Line Performance Solutions</a>) where I pre-computed a big table of possible values from a complicated statistical function, and just looked up an approximate number quickly instead of spending 5-10 seconds computing the exact value.</dd>
<dt>Are you using a bloated CMS (content management system)?</dt>
<dd>If you are and the web pages are static, then a page-caching system will work wonders.</dd>
<dt>Is the server limited by bandwidth, RAM, or CPU?</dt>
<dd>Monitor server performance indicators to see if  there is too much disk thrashing.  Hardware is relatively cheap nowadays, and may be a better solution than spending days unsuccessfully tweaking code.</dd>
<dt>Does the page <em>feel</em> like it loads fast enough?</dt>
<dd>Perception is everything to a web user.  Some non-essential web page items could be changed to load a few seconds after the visible part of the web page is loaded and refreshed.  For example, there&#8217;s no reason to preload all images in a Javascript-based slide show.</dd>
<dt>Is there just too much traffic to your site?</dt>
<dd>Wow &#8211; what a great problem to have!  Maybe it&#8217;s time to scale your web application to multiple servers or host your application in the cloud.  There are sometimes session-management issues when using multiple web servers, but some real smart folks have figured most of this out for the major languages and popular frameworks.</dd>
</dl>
<p>There are only some ideas of what to look for &#8211; and I&#8217;m sure I missed a bunch of simple ones.  As you can see, I recommend spending time diagnosing the real problem before jumping in and optimizing code right away.</p>
<p>I&#8217;d rather have a correct program than a fast incorrect one.  Optimizing code when needed is better than having to debug faulty code.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/05/when-to-optimize/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Server Backups With Minimal Bandwidth</title>
		<link>http://blog.GORGES.us/2009/04/server-backups-with-minimal-bandwidth/</link>
		<comments>http://blog.GORGES.us/2009/04/server-backups-with-minimal-bandwidth/#comments</comments>
		<pubDate>Fri, 10 Apr 2009 14:00:46 +0000</pubDate>
		<dc:creator>Matt Clark</dc:creator>
				<category><![CDATA[System Administration]]></category>
		<category><![CDATA[backups]]></category>
		<category><![CDATA[rsync]]></category>

		<guid isPermaLink="false">http://blog.GORGES.us/?p=56</guid>
		<description><![CDATA[Most of our production servers are at our main co-location facility.  All these servers have second ethernet cards so monitoring and between-server file transfers will not interfere with normal web and e-mail traffic.  This &#8220;shadow network&#8221; is used for our nightly backups. One server has a lot of disk capacity and is the backup device.  [...]]]></description>
			<content:encoded><![CDATA[<p>Most of our production servers are at our main co-location facility.  All these servers have second ethernet cards so monitoring and between-server file transfers will not interfere with normal web and e-mail traffic. <img class="alignright size-medium wp-image-55" title="hard drive" src="http://blog.GORGES.us/wp-content/uploads/2009/03/disk-drive-300x245.jpg" alt="hard drive" width="180" height="147" /> This &#8220;shadow network&#8221; is used for our nightly backups.</p>
<p>One server has a lot of disk capacity and is the backup device.  For years we did a monthly/weekly/daily archive of each server over the shadow network.</p>
<p>However we expanded to a second co-location facility, and also run a &#8220;grid&#8221; machine at a 3rd site.  Backups now started eating a lot of bandwidth, and the monthly backups were not completing during the overnight hours.</p>
<p>So what to do?  Web sites consist mostly of files that do not often change, so really most of the files do not have to be backed up daily.</p>
<p>Our solution was to have our backup system mirror the other server files, and then at night only copy over the changed files.  We used the <strong>rsync</strong> utility to determine and copy the changed files, and then a daily archive is created that compresses the files into a single archive.  This solution also means less processing on the production servers.</p>
<p>Once the files are synchronized on the backup server, then a compressed archive is created and stored away.</p>
<p>There is also a filtering done on the files so that we do not back up temporary files or non-critical system files.</p>
<p>The end result is that we use our bandwidth packets sparingly.  We have backup archives, without saturating our Internet connections getting them offsite.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.GORGES.us/2009/04/server-backups-with-minimal-bandwidth/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
